mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-13 11:13:15 +02:00
474 lines
12 KiB
PHP
474 lines
12 KiB
PHP
{%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;
|