{%MainUnit ../graphics.pp} { TPicture and help classes TPictureFileFormatList ***************************************************************************** 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. ***************************************************************************** } type { TPicFileFormatsList } PPicFileFormat = ^TPicFileFormat; TPicFileFormat = record GraphicClass: TGraphicClass; Extension: string; // ; low case separated list, first is default Description: string; end; TPicFileFormatsList = class(TList) // list of TPicFileFormat public constructor Create; procedure Clear; override; procedure Delete(Index: Integer); procedure Add(const Ext, Desc: String; AClass: TGraphicClass); function GetFormats(Index: integer): PPicFileFormat; function GetFormatExt(Index: integer): String; function GetFormatFilter(Index: integer): String; function FindExt(const Ext: string): TGraphicClass; function FindClassName(const AClassname: string): TGraphicClass; function FindByStreamFormat(Stream: TStream): TGraphicClass; procedure Remove(AClass: TGraphicClass); procedure BuildFilterStrings(GraphicClass: TGraphicClass; var Descriptions, Filters: string); property Formats[Index: integer]: PPicFileFormat read GetFormats; default; end; function GetLocalizedFormatDescription(const GraphicClass: TGraphicClass; const OriginalDescription: string): string; begin case GraphicClass.ClassName of 'TPortableNetworkGraphic': Result:=rsPortableNetworkGraphic; 'TPixmap': Result:=rsPixmap; 'TBitmap': Result:=rsBitmaps; 'TCursorImage': Result:=rsCursor; 'TIcon': Result:=rsIcon; 'TIcnsIcon': Result:=rsIcns; 'TJpegImage': Result:=rsJpeg; 'TTiffImage': Result:=rsTiff; 'TGIFImage': Result:=rsGIF; 'TPortableAnyMapGraphic': Result:=rsPortablePixmap; 'TTGAImage': Result:=rsTGA; otherwise Result:=OriginalDescription; end; end; constructor TPicFileFormatsList.Create; begin inherited Create; // add by priority of use in LCL/IDE Add(TPortableNetworkGraphic.GetFileExtensions, rsPortableNetworkGraphic, TPortableNetworkGraphic); Add(TPixmap.GetFileExtensions, rsPixmap, TPixmap); Add(TBitmap.GetFileExtensions, rsBitmaps, TBitmap); Add(TCursorImage.GetFileExtensions, rsCursor, TCursorImage); Add(TIcon.GetFileExtensions, rsIcon, TIcon); Add(TIcnsIcon.GetFileExtensions, rsIcns, TIcnsIcon); {$IFNDEF DisableLCLJPEG} Add(TJpegImage.GetFileExtensions, rsJpeg, TJpegImage); {$ENDIF} {$IFNDEF DisableLCLTIFF} Add(TTiffImage.GetFileExtensions, rsTiff, TTiffImage); {$ENDIF} {$IFNDEF DisableLCLGIF} Add(TGIFImage.GetFileExtensions, rsGIF, TGIFImage); {$ENDIF} {$IFNDEF DisableLCLPNM} Add(TPortableAnyMapGraphic.GetFileExtensions, rsPortablePixmap, TPortableAnyMapGraphic); {$ENDIF} end; procedure TPicFileFormatsList.Clear; var i: integer; P: PPicFileFormat; begin for i:=0 to Count - 1 do begin P := GetFormats(i); Dispose(P); end; inherited Clear; end; procedure TPicFileFormatsList.Delete(Index: Integer); var P: PPicFileFormat; begin P:=GetFormats(Index); Dispose(P); inherited Delete(Index); end; procedure TPicFileFormatsList.Add(const Ext, Desc: String; AClass: TGraphicClass); var NewFormat: PPicFileFormat; begin New(NewFormat); with NewFormat^ do begin Extension := AnsiLowerCase(Ext); GraphicClass := AClass; Description := Desc; end; inherited Add(NewFormat); end; function TPicFileFormatsList.GetFormats(Index: integer): PPicFileFormat; begin Result:=PPicFileFormat(Items[Index]); end; function TPicFileFormatsList.GetFormatExt(Index: integer): String; begin Result := PPicFileFormat(Items[Index])^.Extension; if Pos(';', Result) > 0 then System.Delete(Result, Pos(';', Result), MaxInt); end; function TPicFileFormatsList.GetFormatFilter(Index: integer): String; begin Result := StringReplace('*.' + PPicFileFormat(Items[Index])^.Extension, ';', ';*.', [rfReplaceAll]); end; function TPicFileFormatsList.FindExt(const Ext: string): TGraphicClass; var I, P: Integer; E, ExtList: String; begin if Ext<>'' then begin E := AnsiLowerCase(Ext); if E[1] = '.' then System.Delete(E, 1, 1); for I := Count - 1 downto 0 do with Formats[I]^ do if Pos(E, Extension) > 0 then begin ExtList := Extension; repeat P := Pos(';', ExtList); if (P = 0) and (ExtList = E) or (Pos(E + ';', ExtList) = 1) then begin Result := GraphicClass; Exit; end; System.Delete(ExtList, 1, P); until P = 0; end; end; Result := nil; end; function TPicFileFormatsList.FindClassName(const AClassName: string): TGraphicClass; var I: Integer; begin // search backwards so that new formats will be found first for I := Count-1 downto 0 do begin Result := GetFormats(I)^.GraphicClass; if AnsiCompareText(Result.ClassName,AClassname)=0 then Exit; end; Result := nil; end; function TPicFileFormatsList.FindByStreamFormat(Stream: TStream): TGraphicClass; var I: Integer; begin for I := 0 to Count - 1 do begin Result := GetFormats(I)^.GraphicClass; if Result.IsStreamFormatSupported(Stream) then Exit; end; Result := nil; end; procedure TPicFileFormatsList.Remove(AClass: TGraphicClass); // remove all file formats which inherits from AClass var I: Integer; P: PPicFileFormat; begin for I := Count - 1 downto 0 do begin P := GetFormats(I); if P^.GraphicClass.InheritsFrom(AClass) then Delete(I); end; end; procedure TPicFileFormatsList.BuildFilterStrings(GraphicClass: TGraphicClass; var Descriptions, Filters: string); var C, I: Integer; P: PPicFileFormat; Filter: String; begin Descriptions := ''; Filters := ''; C := 0; for I := 0 to Count - 1 do begin P := GetFormats(I); if P^.GraphicClass.InheritsFrom(GraphicClass) and (P^.Extension <> '') then with P^ do begin if C <> 0 then begin Descriptions := Descriptions + '|'; Filters := Filters + ';'; end; Filter := GetFormatFilter(I); FmtStr(Descriptions, '%s%s (%s)|%s', [Descriptions, GetLocalizedFormatDescription(GraphicClass, Description), Filter, Filter]); FmtStr(Filters, '%s%s', [Filters, Filter]); Inc(C); end; end; if C > 1 then FmtStr(Descriptions, '%s (%s)|%1:s|%s', [rsGraphic, Filters, Descriptions]); end; //------------------------------------------------------------------------------ type PPicClipboardFormat = ^TPicClipboardFormat; TPicClipboardFormat = record GraphicClass: TGraphicClass; FormatID: TClipboardFormat; end; TPicClipboardFormats = class(TList) // list of TPicClipboardFormat private function GetFormats(Index: integer): PPicClipboardFormat; public constructor Create; procedure Clear; override; procedure Delete(Index: Integer); procedure Add(AFormatID: TClipboardFormat; AClass: TGraphicClass); function FindFormat(FormatID: TClipboardFormat): TGraphicClass; procedure Remove(AClass: TGraphicClass); property Formats[Index: integer]: PPicClipboardFormat read GetFormats; default; end; function TPicClipboardFormats.GetFormats(Index: integer): PPicClipboardFormat; begin Result:=PPicClipboardFormat(Items[Index]); end; constructor TPicClipboardFormats.Create; const sMimeTypePng = 'image/png'; sMimeTypeJpg = 'image/jpeg'; begin inherited Create; Add(PredefinedClipboardFormat(pcfBitmap), TBitmap); Add(PredefinedClipboardFormat(pcfPixmap), TPixmap); //Add(PredefinedClipboardFormat(pcfIcon), TCustomIcon); Add(ClipboardRegisterFormat(sMimeTypePng), TPortableNetworkGraphic); {$IFNDEF DisableLCLJPEG} Add(ClipboardRegisterFormat(sMimeTypeJpg), TJPegImage); {$ENDIF} end; procedure TPicClipboardFormats.Clear; var i: integer; P: PPicClipboardFormat; begin for i := 0 to Count - 1 do begin P := GetFormats(i); Dispose(P); end; inherited Clear; end; procedure TPicClipboardFormats.Delete(Index: Integer); var P: PPicClipboardFormat; begin P := GetFormats(Index); Dispose(P); inherited Delete(Index); end; procedure TPicClipboardFormats.Add(AFormatID: TClipboardFormat; AClass: TGraphicClass); var NewFormat: PPicClipboardFormat; begin if AFormatID=0 then exit; New(NewFormat); with NewFormat^ do begin GraphicClass:=AClass; FormatID:=AFormatID; end; inherited Add(NewFormat); end; function TPicClipboardFormats.FindFormat( FormatID: TClipboardFormat): TGraphicClass; var I: Integer; P: PPicClipboardFormat; begin for I := Count-1 downto 0 do begin P:=GetFormats(i); if P^.FormatID=FormatID then begin Result:=P^.GraphicClass; Exit; end; end; Result := nil; end; procedure TPicClipboardFormats.Remove(AClass: TGraphicClass); var I: Integer; begin for I := Count-1 downto 0 do if GetFormats(i)^.GraphicClass.InheritsFrom(AClass) then Delete(i); end; //------------------------------------------------------------------------------ var PicClipboardFormats: TPicClipboardFormats=nil; PicFileFormats: TPicFileFormatsList=nil; function GetPicFileFormats: TPicFileFormatsList; begin if not Assigned(PicFileFormats) and not GraphicsFinalized then PicFileFormats := TPicFileFormatsList.Create; Result := PicFileFormats; end; function GetPicClipboardFormats: TPicClipboardFormats; begin if (PicClipboardFormats = nil) and (not GraphicsFinalized) then PicClipboardFormats := TPicClipboardFormats.Create; Result := PicClipboardFormats; end; function GraphicFilter(GraphicClass: TGraphicClass): string; var Filters: string; begin Result := ''; GetPicFileFormats.BuildFilterStrings(GraphicClass,Result,Filters); end; function GraphicExtension(GraphicClass: TGraphicClass): string; var I: Integer; PicFormats: TPicFileFormatsList; begin PicFormats := GetPicFileFormats; for I := PicFormats.Count-1 downto 0 do if PicFormats[I]^.GraphicClass.ClassName = GraphicClass.ClassName then begin Result := PicFormats.GetFormatExt(I); Exit; end; Result := ''; end; function GraphicFileMask(GraphicClass: TGraphicClass): string; var Descriptions: string; begin Result := ''; GetPicFileFormats.BuildFilterStrings(GraphicClass,Descriptions,Result); end; function GetGraphicClassForFileExtension(const FileExt: string): TGraphicClass; begin Result:=GetPicFileFormats.FindExt(FileExt); end; //--TPicture-------------------------------------------------------------------- constructor TPicture.Create; begin inherited Create; GetPicFileFormats; GetPicClipboardFormats; end; destructor TPicture.Destroy; begin FGraphic.Free; inherited Destroy; end; procedure TPicture.AssignTo(Dest: TPersistent); begin if Graphic is Dest.ClassType then Dest.Assign(Graphic) else inherited AssignTo(Dest); end; procedure TPicture.ForceType(GraphicType: TGraphicClass); var NewGraphic: TGraphic; begin if not (FGraphic is GraphicType) then begin NewGraphic := GraphicType.Create; NewGraphic.Assign(FGraphic); FGraphic.Free; FGraphic := NewGraphic; FGraphic.OnChange := @Changed; FGraphic.OnProgress := @Progress; Changed(Self); end; end; function TPicture.GetBitmap: TBitmap; begin ForceType(TBitmap); Result := TBitmap(Graphic); end; function TPicture.GetPNG: TPortableNetworkGraphic; begin ForceType(TPortableNetworkGraphic); Result := TPortableNetworkGraphic(Graphic); end; {$IFNDEF DisableLCLPNM} function TPicture.GetPNM: TPortableAnyMapGraphic; begin ForceType(TPortableAnyMapGraphic); Result := TPortableAnyMapGraphic(Graphic); end; {$ENDIF} function TPicture.GetPixmap: TPixmap; begin ForceType(TPixmap); Result := TPixmap(Graphic); end; function TPicture.GetIcon: TIcon; begin ForceType(TIcon); Result := TIcon(Graphic); end; {$IFNDEF DisableLCLJPEG} function TPicture.GetJpeg: TJpegImage; begin ForceType(TJpegImage); Result := TJpegImage(Graphic); end; {$ENDIF} procedure TPicture.SetBitmap(Value: TBitmap); begin SetGraphic(Value); end; procedure TPicture.SetPNG(const AValue: TPortableNetworkGraphic); begin SetGraphic(AValue); end; {$IFNDEF DisableLCLPNM} procedure TPicture.SetPNM(const AValue: TPortableAnyMapGraphic); begin SetGraphic(AValue); end; {$ENDIF} procedure TPicture.SetPixmap(Value: TPixmap); begin SetGraphic(Value); end; procedure TPicture.SetIcon(Value: TIcon); begin SetGraphic(Value); end; {$IFNDEF DisableLCLJPEG} procedure TPicture.SetJpeg(Value: TJpegImage); begin SetGraphic(Value); end; {$ENDIF} procedure TPicture.SetGraphic(Value: TGraphic); var NewGraphic: TGraphic; ok: boolean; begin if (Value=FGraphic) then exit; NewGraphic := nil; ok := False; try if Value <> nil then begin NewGraphic := TGraphicClass(Value.ClassType).Create; NewGraphic.Assign(Value); NewGraphic.OnChange := @Changed; NewGraphic.OnProgress := @Progress; end; FGraphic.Free; FGraphic := NewGraphic; Changed(Self); ok := True; finally // this try..finally construction will in case of an exception // not alter the error backtrace output if not ok then NewGraphic.Free; end; end; { Based on the extension of Filename, create the corresponding TGraphic class and call its LoadFromFile method. } procedure TPicture.LoadFromFile(const Filename: string); var Ext: string; Stream: TStream; begin Ext := ExtractFileExt(Filename); System.Delete(Ext, 1, 1); // delete '.' Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite); try if Ext <> '' then LoadFromStreamWithFileExt(Stream, Ext) else LoadFromStream(Stream); finally Stream.Free; end; end; procedure TPicture.LoadFromResourceName(Instance: TLCLHandle; const ResName: String); var NewGraphic: TGraphic; begin NewGraphic := CreateGraphicFromResourceName(Instance, ResName); FGraphic.Free; FGraphic := NewGraphic; FGraphic.OnChange := @Changed; Changed(Self); end; procedure TPicture.LoadFromResourceName(Instance: TLCLHandle; const ResName: String; AClass: TGraphicClass); var NewGraphic: TGraphic; ok: Boolean; begin NewGraphic := AClass.Create; ok:=false; try NewGraphic.OnProgress := @Progress; NewGraphic.LoadFromResourceName(Instance, ResName); ok:=true; finally // this try..finally construction will in case of an exception // not alter the error backtrace output if not ok then NewGraphic.Free; end; FGraphic.Free; FGraphic := NewGraphic; FGraphic.OnChange := @Changed; Changed(Self); end; procedure TPicture.LoadFromLazarusResource(const AName: string); var Stream: TLazarusResourceStream; begin Stream := TLazarusResourceStream.Create(AName, nil); try LoadFromStreamWithFileExt(Stream, Stream.Res.ValueType); finally Stream.Free; end; end; procedure TPicture.LoadFromStream(Stream: TStream); var GraphicClass: TGraphicClass; begin GraphicClass := GetPicFileFormats.FindByStreamFormat(Stream); if GraphicClass = nil then raise EInvalidGraphic.Create(rsUnknownPictureFormat); LoadFromStreamWithClass(Stream, GraphicClass); end; procedure TPicture.SaveToFile(const Filename: string; const FileExt: string = ''); var Ext: string; Stream: TStream; begin if FileExt <> '' then Ext := AnsiLowerCase(FileExt) else Ext := AnsiLowerCase(ExtractFileExt(Filename)); if (Ext <> '') and (Ext[1] = '.') then System.Delete(Ext, 1, 1); // delete '.' Stream := TFileStream.Create(Filename, fmCreate); try SaveToStreamWithFileExt(Stream, Ext); finally Stream.Free; end; end; procedure TPicture.SaveToStream(Stream: TStream); begin if Assigned(Graphic) then Graphic.SaveToStream(Stream); end; procedure TPicture.SaveToStreamWithFileExt(Stream: TStream; const FileExt: string); var GraphicClass: TGraphicClass; IntfImg: TLazIntfImage; ImgWriter: TFPCustomImageWriter; fpBmp: TFPImageBitmap; begin if Graphic = nil then Exit; if FileExt <> '' then GraphicClass := FindGraphicClassWithFileExt(FileExt); if (FileExt = '') or (Graphic is GraphicClass) then begin Graphic.SaveToStream(Stream); Exit; end; // save in different format if (Graphic is TFPImageBitmap) and GraphicClass.InheritsFrom(TFPImageBitmap) then begin fpBmp := TFPImageBitmap(Graphic); ImgWriter := nil; IntfImg := TLazIntfImage.Create(0,0,[]); try ImgWriter := TFPImageBitmapClass(GraphicClass).GetWriterClass.Create; IntfImg.SetRawImage(fpBmp.GetRawImagePtr^, False); fpBmp.InitializeWriter(IntfImg, ImgWriter); IntfImg.SaveToStream(Stream, ImgWriter); fpBmp.FinalizeWriter(ImgWriter); finally IntfImg.Free; ImgWriter.Free; end; Exit; end; // no conversion available yet raise Exception.CreateFmt('TODO: Conversion for vector or icon images of format "%s" to "%s"!', [Graphic.GetFileExtensions, FileExt]); end; procedure TPicture.LoadFromStreamWithFileExt(Stream: TStream; const FileExt: string); begin LoadFromStreamWithClass(Stream, FindGraphicClassWithFileExt(FileExt)); end; procedure TPicture.LoadFromClipboardFormat(FormatID: TClipboardFormat); begin LoadFromClipboardFormatID(ctClipboard,FormatID); end; procedure TPicture.LoadFromClipboardFormatID(ClipboardType: TClipboardType; FormatID: TClipboardFormat); var NewGraphic: TGraphic; GraphicClass: TGraphicClass; ok: boolean; begin GraphicClass := PicClipboardFormats.FindFormat(FormatID); if GraphicClass = nil then raise EInvalidGraphic.CreateFmt(rsUnsupportedClipboardFormat, [ClipboardFormatToMimeType(FormatID)]); NewGraphic := GraphicClass.Create; ok:=false; try NewGraphic.OnProgress := @Progress; NewGraphic.LoadFromClipboardFormatID(ClipboardType,FormatID); ok:=true; finally if not ok then NewGraphic.Free; end; FGraphic.Free; FGraphic := NewGraphic; FGraphic.OnChange := @Changed; Changed(Self); end; procedure TPicture.SaveToClipboardFormat(FormatID: TClipboardFormat); begin if FGraphic <> nil then FGraphic.SaveToClipboardFormat(FormatID); end; class function TPicture.SupportsClipboardFormat(FormatID: TClipboardFormat): Boolean; begin Result := GetPicClipboardFormats.FindFormat(FormatID) <> nil; end; procedure TPicture.Assign(Source: TPersistent); begin if Source = nil then SetGraphic(nil) else if Source is TPicture then SetGraphic(TPicture(Source).Graphic) else if Source is TGraphic then SetGraphic(TGraphic(Source)) else if Source is TFPCustomImage then Bitmap.Assign(Source) else inherited Assign(Source); end; class procedure TPicture.RegisterFileFormat(const AnExtension, ADescription: string; AGraphicClass: TGraphicClass); begin GetPicFileFormats.Add(AnExtension, ADescription, AGraphicClass); end; class procedure TPicture.RegisterClipboardFormat(FormatID: TClipboardFormat; AGraphicClass: TGraphicClass); begin GetPicClipboardFormats.Add(FormatID, AGraphicClass); end; class procedure TPicture.UnregisterGraphicClass(AClass: TGraphicClass); begin if PicFileFormats <> nil then PicFileFormats.Remove(AClass); if PicClipboardFormats <> nil then PicClipboardFormats.Remove(AClass); end; procedure TPicture.Clear; begin SetGraphic(nil); end; class function TPicture.FindGraphicClassWithFileExt(const Ext: string; ExceptionOnNotFound: boolean): TGraphicClass; var FileExt: String; begin FileExt := Ext; if (FileExt <> '') and (FileExt[1] = '.') then FileExt := Copy(FileExt, 2, length(FileExt)); Result := GetPicFileFormats.FindExt(FileExt); if (Result = nil) and ExceptionOnNotFound then raise EInvalidGraphic.CreateFmt(rsUnknownPictureExtension, [Ext]); end; procedure TPicture.Changed(Sender: TObject); begin if Assigned(FOnChange) then FOnChange(Self); end; procedure TPicture.Progress(Sender: TObject; Stage: TProgressStage; PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string; var DoContinue: boolean); begin DoContinue:=true; if Assigned(FOnProgress) then FOnProgress(Sender, Stage, PercentDone, RedrawNow, R, Msg, DoContinue); end; procedure TPicture.LoadFromStreamWithClass(Stream: TStream; AClass: TGraphicClass); var NewGraphic: TGraphic; ok: Boolean; begin NewGraphic := AClass.Create; ok:=false; try NewGraphic.OnProgress := @Progress; NewGraphic.LoadFromStream(Stream); ok:=true; finally // this try..finally construction will in case of an exception // not alter the error backtrace output if not ok then NewGraphic.Free; end; FGraphic.Free; FGraphic := NewGraphic; FGraphic.OnChange := @Changed; Changed(Self); end; procedure TPicture.ReadData(Stream: TStream); var GraphicClassName: Shortstring; NewGraphic: TGraphic; GraphicClass: TGraphicClass; ok: boolean; isRegisteredFormat: Boolean = true; begin Stream.Read(GraphicClassName[0], 1); Stream.Read(GraphicClassName[1], length(GraphicClassName)); GraphicClass := GetPicFileFormats.FindClassName(GraphicClassName); if GraphicClass = nil then begin // This case happens when the stream does not contain the name of a registered graphic class isRegisteredFormat := false; // In Delphi the image data follow immediately after the graphic class name GraphicClass := GetPicFileFormats.FindByStreamFormat(Stream); if GraphicClass = nil then begin // In Lazarus we must skip 4 bytes (image size) to get to the image data. Stream.ReadDWord; GraphicClass := GetPicFileFormats.FindByStreamFormat(Stream); end; end; NewGraphic := nil; if GraphicClass <> nil then begin NewGraphic := GraphicClass.Create; ok:=false; try if isRegisteredFormat then NewGraphic.ReadData(Stream) else NewGraphic.LoadFromStream(Stream); ok:=true; finally if not ok then NewGraphic.Free; end; end; FGraphic.Free; FGraphic := NewGraphic; if NewGraphic <> nil then begin NewGraphic.OnChange := @Changed; NewGraphic.OnProgress := @Progress; end; Changed(Self); end; procedure TPicture.WriteData(Stream: TStream); var GraphicClassName: ShortString; begin with Stream do begin if Graphic <> nil then GraphicClassName := Graphic.ClassName else GraphicClassName := ''; Write(GraphicClassName, Length(GraphicClassName) + 1); if Graphic <> nil then Graphic.WriteData(Stream); end; end; procedure TPicture.DefineProperties(Filer: TFiler); function DoWrite: Boolean; var Ancestor: TPicture; begin if Filer.Ancestor is TPicture then begin Ancestor := TPicture(Filer.Ancestor); if not Assigned(Graphic) then Exit(Assigned(Ancestor.Graphic)); Result := not Graphic.Equals(Ancestor.Graphic); end else Result := Assigned(Graphic); end; begin Filer.DefineBinaryProperty('Data', @ReadData, @WriteData, DoWrite); end; function TPicture.GetWidth: Integer; begin if FGraphic <> nil then Result := FGraphic.Width else Result := 0; end; function TPicture.GetHeight: Integer; begin Result := 0; if FGraphic <> nil then Result := FGraphic.Height else Result := 0; end; // included by graphics.pp