{%MainUnit ../graphics.pp} {****************************************************************************** TicnsIcon ****************************************************************************** ***************************************************************************** This file is part of the Lazarus Component Library (LCL) See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** } { TIcnsList } function TIcnsList.GetItem(Index: Integer): PIcnsRec; begin Result := inherited Get(Index); end; procedure TIcnsList.SetItem(Index: Integer; const AValue: PIcnsRec); begin inherited Put(Index, AValue); end; procedure TIcnsList.Notify(Ptr: Pointer; Action: TListNotification); begin if Action = lnDeleted then Dispose(PIcnsRec(Ptr)); inherited Notify(Ptr, Action); end; function TIcnsList.Add(AIconType: TicnsIconType; ARawImage: TRawImage): Integer; var Rec: PIcnsRec; begin New(Rec); Rec^.IconType := AIconType; Rec^.RawImage := ARawImage; Result := inherited Add(Rec); end; { TIcnsIcon } procedure TIcnsIcon.IcnsAdd(AIconType: TicnsIconType; ARawImage: TRawImage); function GetMaskList: TIcnsList; begin if FMaskList = nil then FMaskList := TIcnsList.Create; Result := FMaskList; end; function GetImageList: TIcnsList; begin if FImageList = nil then FImageList := TIcnsList.Create; Result := FImageList; end; begin if AIconType in icnsMaskTypes then GetMaskList.Add(AIconType, ARawImage) else GetImageList.Add(AIconType, ARawImage); end; procedure TIcnsIcon.IcnsProcess; procedure MergeMask(var AImage, AMask: TRawImage); var LazIntfImage, LazIntfMask: TLazIntfImage; Col, Row: Integer; Color: TFpColor; begin if AMask.Description.Depth = 1 then begin // merge mask AImage.Description.MaskBitOrder := AMask.Description.MaskBitOrder; AImage.Description.MaskLineEnd := AMask.Description.MaskLineEnd; AImage.Description.MaskBitsPerPixel := AMask.Description.MaskBitsPerPixel; AImage.Description.MaskShift := AMask.Description.MaskShift; AImage.MaskSize := AMask.MaskSize; AImage.Mask := ReallocMem(AImage.Mask, AMask.MaskSize); Move(AMask.Mask^, AImage.Mask^, AMask.MaskSize); end else begin LazIntfImage := TLazIntfImage.Create(AImage, False); LazIntfMask := TLazIntfImage.Create(AMask, False); for Row := 0 to LazIntfImage.Height - 1 do for Col := 0 to LazIntfImage.Width - 1 do begin Color := LazIntfImage.Colors[Col,Row]; Color.alpha := LazIntfMask.Colors[Col,Row].alpha; LazIntfImage.Colors[Col,Row] := Color; end; LazIntfMask.Free; LazIntfImage.Free; end; end; var i, AIndex: integer; ImagesForMask: TicnsIconTypes; IconImage: TIconImage; begin // merge separate image and masc rawdata together if FMaskList <> nil then begin for i := 0 to FMaskList.Count - 1 do begin ImagesForMask := icnsMaskToImageMap[FMaskList[i]^.IconType]; for AIndex := 0 to FImageList.Count - 1 do if FImageList[AIndex]^.IconType in ImagesForMask then MergeMask(FImageList[AIndex]^.RawImage, FMaskList[i]^.RawImage); // dispose RawImage since no more needed FMaskList[i]^.RawImage.FreeData; end; FreeAndNil(FMaskList); end; for i := 0 to FImageList.Count - 1 do begin if FImageList[i]^.IconType in icnsWithAlpha then begin // todo: we have no jpeg 2000 reader to decompress their data => skip for now FImageList[i]^.RawImage.FreeData; Continue; end; // Add image with TSharedIcon(FSharedImage) do begin IconImage := GetImagesClass.Create(FImageList[i]^.RawImage); Add(IconImage); end; end; FreeAndNil(FImageList); CheckRequestedSize; FCurrent := GetBestIndexForSize(FRequestedSize); end; class function TIcnsIcon.GetSharedImageClass: TSharedRasterImageClass; begin Result := TSharedIcnsIcon; end; constructor TIcnsIcon.Create; begin inherited Create; FImageList := nil; FMaskList := nil; end; destructor TIcnsIcon.Destroy; begin inherited Destroy; FImageList.Free; FMaskList.Free; end; procedure TIcnsIcon.ReadData(Stream: TStream); var Resource: TIconFamilyResource; Position: Int64; begin Position := Stream.Position; Stream.Read(Resource, SizeOf(Resource)); if Resource.resourceType = kIconFamilyType then begin Stream.Position := Position; LoadFromStream(Stream, BEtoN(Resource.resourceSize)) end else begin Stream.Position := Position; LoadFromStream(Stream); end; end; procedure TIcnsIcon.ReadStream(AStream: TMemoryStream; ASize: Longint); var Resource: TIconFamilyResource; IntfImage: TLazIntfImage; ImgReader: TLazReaderIcnsPart; LazReader: ILazImageReader; RawImg: TRawImage; begin AStream.Read(Resource, SizeOf(Resource)); if (Resource.resourceType <> kIconFamilyType) then raise EInvalidGraphic.Create('Stream is not an ICNS type'); IntfImage := nil; ImgReader := nil; Resource.resourceSize := BEtoN(Resource.resourceSize); if ASize > Resource.resourceSize then ASize := Resource.resourceSize; while AStream.Position < ASize do begin if IntfImage = nil then IntfImage := TLazIntfImage.Create(0,0,[]) else IntfImage.SetSize(0,0); if ImgReader = nil then ImgReader := TLazReaderIcnsPart.Create; if Supports(ImgReader, ILazImageReader, LazReader) then LazReader.UpdateDescription := True else IntfImage.DataDescription := QueryDescription([riqfRGB, riqfAlpha, riqfMask]); // fallback to default ImgReader.ImageRead(AStream, IntfImage); IntfImage.GetRawImage(RawImg, True); IcnsAdd(ImgReader.IconType, RawImg); end; LazReader := nil; IntfImage.Free; ImgReader.Free; IcnsProcess; end; // only 24 bit RGB is RLE encoded the same way as TIFF or TGA RLE // data is encoded channel by channel: // high bit = 0 => length = low 0..6 bits + 1; read length times next value // high bit = 1 => length = value - 125 ; read one value and repeat length times function EncodeTiffRLE(const Src: array of byte; Offset, Count: Integer; var Dst: array of Byte; DstOffset: Integer): Integer; var cnt : Integer; i,j,k : Integer; d : Integer; last : Integer; const LenRLEOffset = 125; MaxRLEDiff = 255-LenRLEOffset; begin if Count = 0 then begin Result := 0; Exit; end; i := Offset; d := DstOffset; last := Offset+Count; while i < last do begin if (i < last-2) and (Src[i] = Src[i+1]) and (Src[i]=Src[i+2]) then begin j := i; inc(i); while (i < last) and (Src[i] = Src[i-1]) do inc(i); cnt := i - j; while cnt > 0 do begin k := Min(MaxRLEDiff, cnt); if k > 2 then begin Dst[d] := byte(k+LenRLEOffset); inc(d); Dst[d] := Src[j]; inc(d); dec(cnt, k); end else begin dec(i,k); cnt := 0; k := 0; end; end; end else begin j := i; if (i < last-1) and (Src[i] = Src[i+1]) then inc(i); if last-i > 2 then begin inc(i); while (i < last) and (Src[i] <> Src[i-1]) do inc(i); if i < last then dec(i); end else i := last; cnt := i - j; while cnt > 0 do begin k := Min(128, cnt); Dst[d] := k-1; inc(d); Move(Src[j], Dst[d], k); inc(j, k); inc(d, k); dec(cnt, k); end; end; end; Result := d - DstOffset; end; { !!! WARNING !!! the following code might be INTEL ONLY! Needs to be tested on PowerPC } function CompressRGBImage(RGBAImage: TLazIntfImage; Stream: TStream): Int64; var src : array of byte; dst : array of byte; i : Integer; raw : TRawImage; sz : Integer; pb : PByteArray; k : Integer; j : Integer; StreamPos : Int64; begin StreamPos := Stream.Position; sz := RGBAImage.Width*RGBAImage.Height; SetLength(src, sz); SetLength(dst, sz*2); RGBAImage.GetRawImage(raw, false); pb:=PByteArray(raw.Data); { red , green, blue values are in separate RLE blocks } for i := 0 to 2 do begin k:=i+1; {the first element is Alpha, skip it} for j := 0 to sz - 1 do begin src[j]:=pb^[k]; inc(k,4); end; k := EncodeTiffRLE(src, 0, sz, dst, 0); Stream.Write(dst[0], k); end; Result := Stream.Position-StreamPos; Stream.Position:=StreamPos; end; { !!! WARNING !!! the following code might be INTEL ONLY! Needs to be tested on PowerPC } function CompressMaskImage(RGBAImage: TLazIntfImage; Stream: TStream): Int64; var src : array of Byte; i : Integer; arr : PIntegerArray; row : TRawImage; StreamPos : Int64; begin StreamPos := Stream.Position; SetLength(src, RGBAImage.Width*RGBAImage.Height); RGBAImage.GetRawImage(row, false); arr := PIntegerArray(row.Data); for i := 0 to length(src) - 1 do //src[i] := byte((arr^[i] shr 24) and $FF); src[i] := byte( arr^[i] and $FF); //src[i]:=255; Stream.Write(src[0], length(src)); Result := Stream.Position-StreamPos; Stream.Position:=StreamPos; end; procedure TIcnsIcon.WriteStream(AStream: TMemoryStream); var mem : array [0..63] of TMemoryStream; icnType : TicnsIconType; id : array [0..63] of FourCharCode; FCode : FourCharCode; el : TIconFamilyElement; i, j, n : integer; totalsz : LongWord; ImageCount : Integer; RawImg : TRawImage; IconImage : TIconImage; IntfImage : TLazIntfImage; SrcImage : TLazIntfImage; begin ImageCount := TSharedIcon(FSharedImage).Count; if ImageCount = 0 then Exit; IntfImage:=nil; j := 0; System.FillChar(mem, sizeof(mem), 0); for n := 0 to ImageCount - 1 do begin IconImage := TIconImage(TSharedIcon(FSharedImage).FImages[n]); icnType := GetDataTypeRGB(IconImage.Width, IconImage.Height, FCode); if icnType = iitNone then Continue; {image is improper size. Skip it} IconImage.RawImageNeeded(false); RawImg := IconImage.FImage; IntfImage := TLazIntfImage.Create(IconImage.Width, IconImage.Height, [riqfRGB, riqfAlpha, riqfUpdate]); IntfImage.CreateData; try SrcImage := TLazIntfImage.Create(RawImg, False); try IntfImage.CopyPixels( SrcImage, 0,0, true); finally SrcImage.Free; end; // write image data if (IconImage.Width >= 256) then begin // todo: Jpeg2000 end else begin { compressing RGB data value } id[j] := FCode; mem[j]:= TMemoryStream.Create; // Apple bug? preceding 4 zero-bytes is required for 128x128 icon if IconImage.Width = 128 then mem[j].WriteDWord(0); CompressRGBImage(IntfImage, mem[j]); mem[j].Position:=0; inc(j); { compressing Mask data value } GetMaskType8bit(IconImage.Height, IconImage.Width, id[j]); mem[j]:=TMemoryStream.Create; CompressMaskImage(IntfImage, mem[j]); inc(j); end; finally IntfImage.Free; end; end; if j = 0 then Exit; {no images to write} totalsz := sizeof(TIconFamilyElement); for i := 0 to j - 1 do inc(totalsz, mem[i].Size + sizeof(TIconFamilyElement)); el.elementType := kIconFamilyType; el.elementSize := BEtoN(totalsz); {sizes are big-endian} AStream.Write(el, sizeof(el)); for i := 0 to j - 1 do begin el.elementType := id[i]; el.elementSize := BEtoN( LongWord(mem[i].Size + sizeof(TIconFamilyElement)) ); {sizes are big-endian } AStream.Write( el, sizeof(el) ); AStream.CopyFrom(mem[i], mem[i].Size) end; for i := 0 to j - 1 do mem[i].Free; end; class function TIcnsIcon.GetFileExtensions: string; begin Result := 'icns'; end; function TIcnsIcon.LazarusResourceTypeValid(const ResourceType: string): boolean; begin Result := (UpperCase(ResourceType) = 'ICNS'); end;