|
@@ -4,11 +4,13 @@
|
|
|
{ }
|
|
|
{ CopyRight (C) 2018-2020 KngStr }
|
|
|
{ }
|
|
|
+{ Some from FlyUtils.TBitmapHelper }
|
|
|
+{ IOS code from 凌风 }
|
|
|
+{ }
|
|
|
{*******************************************************}
|
|
|
|
|
|
-unit ksBitmapHelper;
|
|
|
|
|
|
-// 部分拷贝自 FlyUtils.TBitmapHelper
|
|
|
+unit ksBitmapHelper;
|
|
|
|
|
|
{$DEFINE FMX}
|
|
|
|
|
@@ -192,6 +194,10 @@ uses
|
|
|
Androidapi.JNI.GraphicsContentViewText,
|
|
|
Androidapi.JNI.JavaTypes,
|
|
|
{$ENDIF}
|
|
|
+ {$IFDEF IOS}
|
|
|
+ FMX.Graphics.iOS, iOSapi.UIKit, iOSapi.Foundation, iOSapi.CoreGraphics,
|
|
|
+ Macapi.ObjectiveC, FMX.Helpers.iOS, iOSapi.CocoaTypes,
|
|
|
+ {$ENDIF}
|
|
|
{$IFDEF MSWINDOWS}
|
|
|
FMX.Canvas.D2D, Winapi.Wincodec, Winapi.Windows, Winapi.ActiveX,
|
|
|
{$ENDIF}
|
|
@@ -274,6 +280,29 @@ type
|
|
|
function Rotate(const AStream: TStream; const Angle: Single): Boolean; overload;
|
|
|
end;
|
|
|
{$ENDIF}
|
|
|
+ {$IFDEF iOS}
|
|
|
+ TBitmapCodecIOS = class(FMX.Graphics.iOS.TBitmapCodecQuartz)
|
|
|
+ private const
|
|
|
+ DefaultSaveQuality = 75;
|
|
|
+ private
|
|
|
+ class function IsGIFStream(const Stream: TStream): Boolean;
|
|
|
+ function LoadMovieFromStreamScaled(const AStream: TStream;
|
|
|
+ const Surface: TBitmapSurface; const FitSize: TPoint): Boolean;
|
|
|
+ function LoadMovieFromStream(const Stream: TStream;
|
|
|
+ const Surface: TBitmapSurface): Boolean;
|
|
|
+ public
|
|
|
+ class function GetImageSize(const AFileName: string): TPointF; overload;
|
|
|
+ class function GetImageSize(const AStream: TStream): TPointF; overload;
|
|
|
+ function LoadFromStream(const AStream: TStream; const Bitmap: TBitmapSurface;
|
|
|
+ const MaxSizeLimit: Cardinal): Boolean; override;
|
|
|
+ function LoadThumbnailFromStream(const AStream: TStream; const AFitWidth, AFitHeight: Single;
|
|
|
+ const UseEmbedded: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
|
+ function LoadThumbnailFromStream(const AStream: TStream; const ASrc, ADest: TRectF;
|
|
|
+ const UseEmbedded: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
|
+ function Rotate(const Extension: string; const Angle: Single; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
|
+ function Rotate(const AStream: TStream; const Angle: Single): Boolean; overload;
|
|
|
+ end;
|
|
|
+ {$ENDIF}
|
|
|
{$IFDEF MSWINDOWS}
|
|
|
TBitmapCodecWIC = class(FMX.Canvas.D2D.TCustomBitmapCodecWIC)
|
|
|
private
|
|
@@ -1081,7 +1110,11 @@ begin
|
|
|
if CodecClass <> nil then
|
|
|
{$IFDEF ANDROID}
|
|
|
Result := TBitmapCodecAndroid(CodecClass).GetImageSize(AStream)
|
|
|
- {$ELSE}
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF IOS}
|
|
|
+ Result := TBitmapCodecIOS(CodecClass).GetImageSize(AStream)
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF MSWINDOWS}
|
|
|
Result := TBitmapCodecWIC(CodecClass).GetImageSize(AStream)
|
|
|
{$ENDIF}
|
|
|
else
|
|
@@ -1123,7 +1156,11 @@ begin
|
|
|
try
|
|
|
{$IFDEF ANDROID}
|
|
|
Result := TBitmapCodecAndroid(Codec).LoadFromStream(AStream, Bitmap, MaxSizeLimit);
|
|
|
- {$ELSE}
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF IOS}
|
|
|
+ Result := TBitmapCodecIOS(Codec).LoadFromStream(AStream, Bitmap, MaxSizeLimit);
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF MSWINDOWS}
|
|
|
Result := TBitmapCodecWIC(Codec).LoadFromStream(AStream, Bitmap, MaxSizeLimit);
|
|
|
{$ENDIF}
|
|
|
finally
|
|
@@ -1149,7 +1186,11 @@ begin
|
|
|
try
|
|
|
{$IFDEF ANDROID}
|
|
|
Result := TBitmapCodecAndroid(Codec).LoadThumbnailFromStream(AStream, ASrc, ADest, UseEmbedded, Bitmap);
|
|
|
- {$ELSE}
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF IOS}
|
|
|
+ Result := TBitmapCodecIOS(Codec).LoadThumbnailFromStream(AStream, ASrc, ADest, UseEmbedded, Bitmap);
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF MSWINDOWS}
|
|
|
Result := TBitmapCodecWIC(Codec).LoadThumbnailFromStream(AStream, ASrc, ADest, UseEmbedded, Bitmap);
|
|
|
{$ENDIF}
|
|
|
finally
|
|
@@ -1176,7 +1217,11 @@ begin
|
|
|
try
|
|
|
{$IFDEF ANDROID}
|
|
|
Result := TBitmapCodecAndroid(Codec).Rotate(AStream, Angle);
|
|
|
- {$ELSE}
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF IOS}
|
|
|
+ Result := TBitmapCodecIOS(Codec).Rotate(AStream, Angle);
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF MSWINDOWS}
|
|
|
Result := TBitmapCodecWIC(Codec).Rotate(AStream, Angle);
|
|
|
{$ENDIF}
|
|
|
finally
|
|
@@ -1201,7 +1246,11 @@ begin
|
|
|
try
|
|
|
{$IFDEF ANDROID}
|
|
|
Result := TBitmapCodecAndroid(Codec).Rotate(Extension, Angle, Bitmap);
|
|
|
- {$ELSE}
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF IOS}
|
|
|
+ Result := TBitmapCodecIOS(Codec).Rotate(Extension, Angle, Bitmap);
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF MSWINDOWS}
|
|
|
Result := TBitmapCodecWIC(Codec).Rotate(Extension, Angle, Bitmap);
|
|
|
{$ENDIF}
|
|
|
finally
|
|
@@ -1228,7 +1277,11 @@ begin
|
|
|
try
|
|
|
{$IFDEF ANDROID}
|
|
|
Result := TBitmapCodecAndroid(Codec).LoadThumbnailFromStream(AStream, AFitWidth, AFitHeight, UseEmbedded, AutoCut, Bitmap);
|
|
|
- {$ELSE}
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF IOS}
|
|
|
+ Result := TBitmapCodecIOS(Codec).LoadThumbnailFromStream(AStream, AFitWidth, AFitHeight, UseEmbedded, Bitmap);
|
|
|
+ {$ENDIF}
|
|
|
+ {$IFDEF MSWINDOWS}
|
|
|
Result := TBitmapCodecWIC(Codec).LoadThumbnailFromStream(AStream, AFitWidth, AFitHeight, UseEmbedded, AutoCut, Bitmap);
|
|
|
{$ENDIF}
|
|
|
finally
|
|
@@ -2064,4 +2117,245 @@ begin
|
|
|
end;
|
|
|
{$ENDIF}
|
|
|
|
|
|
+{ TBitmapCodecIOS }
|
|
|
+{$IFDEF IOS}
|
|
|
+class function TBitmapCodecIOS.GetImageSize(const AFileName: string): TPointF;
|
|
|
+begin
|
|
|
+ Result := inherited GetImageSize(AFileName);
|
|
|
+end;
|
|
|
+
|
|
|
+class function TBitmapCodecIOS.GetImageSize(const AStream: TStream): TPointF;
|
|
|
+var
|
|
|
+ Img: UIImage;
|
|
|
+ TempStream: TMemoryStream;
|
|
|
+ aData: NSData;
|
|
|
+ SavePosition: Int64;
|
|
|
+begin
|
|
|
+ SavePosition := AStream.Position;
|
|
|
+ try
|
|
|
+ TempStream := TMemoryStream.Create;
|
|
|
+ try
|
|
|
+ AStream.Position := 0;
|
|
|
+ TempStream.CopyFrom(AStream, AStream.Size);
|
|
|
+ aData := TNSData.Wrap(TNSData.alloc.initWithBytesNoCopy(TempStream.Memory,TempStream.Size,False));
|
|
|
+ if aData.length > 0 then
|
|
|
+ begin
|
|
|
+ Img := TUIImage.Wrap(TUIImage.alloc.initWithData(aData));
|
|
|
+ if Img <> nil then
|
|
|
+ try
|
|
|
+ Result := PointF(Img.Size.width, Img.Size.height);
|
|
|
+ finally
|
|
|
+ Img.release;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ Result := TPointF.Zero;
|
|
|
+ end else
|
|
|
+ Result := TPointF.Zero;
|
|
|
+ finally
|
|
|
+ TempStream.free;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ AStream.Position := SavePosition;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+class function TBitmapCodecIOS.IsGIFStream(const Stream: TStream): Boolean;
|
|
|
+begin
|
|
|
+ Result := TImageTypeChecker.GetType(Stream) = SGIFImageExtension;
|
|
|
+end;
|
|
|
+
|
|
|
+function TBitmapCodecIOS.LoadFromStream(const AStream: TStream;
|
|
|
+ const Bitmap: TBitmapSurface; const MaxSizeLimit: Cardinal): Boolean;
|
|
|
+begin
|
|
|
+ Result := inherited LoadFromStream(AStream, Bitmap, MaxSizeLimit);
|
|
|
+end;
|
|
|
+
|
|
|
+function TBitmapCodecIOS.LoadMovieFromStream(const Stream: TStream;
|
|
|
+ const Surface: TBitmapSurface): Boolean;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+end;
|
|
|
+
|
|
|
+function TBitmapCodecIOS.LoadMovieFromStreamScaled(const AStream: TStream;
|
|
|
+ const Surface: TBitmapSurface; const FitSize: TPoint): Boolean;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+end;
|
|
|
+
|
|
|
+function TBitmapCodecIOS.LoadThumbnailFromStream(const AStream: TStream; const AFitWidth, AFitHeight: Single;
|
|
|
+ const UseEmbedded: Boolean; const Bitmap: TBitmapSurface): Boolean;
|
|
|
+var
|
|
|
+ Img: UIImage;
|
|
|
+ ImgRef: CGImageRef;
|
|
|
+ CtxRef: CGContextRef;
|
|
|
+ R: TRectF;
|
|
|
+
|
|
|
+ TempStream: TMemoryStream;
|
|
|
+ aData: NSData;
|
|
|
+ SavePosition: Int64;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+
|
|
|
+ SavePosition := AStream.Position;
|
|
|
+ try
|
|
|
+ TempStream := TMemoryStream.Create;
|
|
|
+ try
|
|
|
+ AStream.Position := 0;
|
|
|
+ TempStream.CopyFrom(AStream, AStream.Size);
|
|
|
+ aData := TNSData.Wrap(TNSData.alloc.initWithBytesNoCopy(TempStream.Memory,TempStream.Size,False));
|
|
|
+ if aData.length > 0 then
|
|
|
+ begin
|
|
|
+ Img := TUIImage.Wrap(TUIImage.alloc.initWithData(aData));
|
|
|
+ if Img <> nil then
|
|
|
+ try
|
|
|
+ ImgRef := Img.cGImage;
|
|
|
+ if ImgRef <> nil then
|
|
|
+ begin
|
|
|
+ R := TRectF.Create(0, 0, CGImageGetWidth(ImgRef), CGImageGetHeight(ImgRef));
|
|
|
+ R.Fit(TRectF.Create(0, 0, AFitWidth, AFitHeight));
|
|
|
+
|
|
|
+ Bitmap.SetSize(Round(R.Width), Round(R.Height), TPixelFormat.RGBA);
|
|
|
+
|
|
|
+ CtxRef := CGBitmapContextCreate(Bitmap.Bits, Bitmap.Width, Bitmap.Height, 8, Bitmap.Pitch, ColorSpace,
|
|
|
+ kCGImageAlphaPremultipliedLast);
|
|
|
+ try
|
|
|
+ CGContextDrawImage(CtxRef, CGRectMake(0, 0, Bitmap.Width, Bitmap.Height), imgRef);
|
|
|
+ finally
|
|
|
+ CGContextRelease(CtxRef);
|
|
|
+ end;
|
|
|
+
|
|
|
+ Result := True;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ Img.release;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ TempStream.free;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ AStream.Position := SavePosition;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TBitmapCodecIOS.LoadThumbnailFromStream(const AStream: TStream;
|
|
|
+ const ASrc, ADest: TRectF; const UseEmbedded: Boolean;
|
|
|
+ const Bitmap: TBitmapSurface): Boolean;
|
|
|
+var
|
|
|
+ Img: UIImage;
|
|
|
+ ImgRef: CGImageRef;
|
|
|
+ CtxRef: CGContextRef;
|
|
|
+ R: TRectF;
|
|
|
+
|
|
|
+ TempStream: TMemoryStream;
|
|
|
+ aData: NSData;
|
|
|
+ SavePosition: Int64;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+
|
|
|
+ SavePosition := AStream.Position;
|
|
|
+ try
|
|
|
+ TempStream := TMemoryStream.Create;
|
|
|
+ try
|
|
|
+ AStream.Position := 0;
|
|
|
+ TempStream.CopyFrom(AStream, AStream.Size);
|
|
|
+ aData := TNSData.Wrap(TNSData.alloc.initWithBytesNoCopy(TempStream.Memory,TempStream.Size,False));
|
|
|
+ if aData.length > 0 then
|
|
|
+ begin
|
|
|
+ Img := TUIImage.Wrap(TUIImage.alloc.initWithData(aData));
|
|
|
+ if Img <> nil then
|
|
|
+ try
|
|
|
+ ImgRef := Img.cGImage;
|
|
|
+ if ImgRef <> nil then
|
|
|
+ begin
|
|
|
+ R := TRectF.Create(0, 0, ASrc.Width, ASrc.Height);
|
|
|
+ R.Fit(TRectF.Create(0, 0, ADest.Width, ADest.Height));
|
|
|
+
|
|
|
+ Bitmap.SetSize(Round(R.Width), Round(R.Height), TPixelFormat.RGBA);
|
|
|
+
|
|
|
+ CtxRef := CGBitmapContextCreate(Bitmap.Bits, Bitmap.Width, Bitmap.Height, 8, Bitmap.Pitch, ColorSpace,
|
|
|
+ kCGImageAlphaPremultipliedLast);
|
|
|
+ try
|
|
|
+ CGContextDrawImage(CtxRef, CGRectMake(0, 0, Bitmap.Width, Bitmap.Height), imgRef);
|
|
|
+ finally
|
|
|
+ CGContextRelease(CtxRef);
|
|
|
+ end;
|
|
|
+
|
|
|
+ Result := True;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ Img.release;
|
|
|
+ end;
|
|
|
+
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ TempStream.free;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ AStream.Position := SavePosition;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TBitmapCodecIOS.Rotate(const Extension: string; const Angle: Single;
|
|
|
+ const Bitmap: TBitmapSurface): Boolean;
|
|
|
+var
|
|
|
+ Img: UIImage;
|
|
|
+ ImageRef: CGImageRef;
|
|
|
+ CtxRef: CGContextRef;
|
|
|
+ BitmapSize: TSize;
|
|
|
+ ColorSpace: CGColorSpaceRef;
|
|
|
+ bp: TBitmap;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+
|
|
|
+ bp := UIImageToBitmap(BitmapSurfaceToUIImage(Bitmap), Angle, TSize.Create(Bitmap.Width, Bitmap.Height));
|
|
|
+ try
|
|
|
+ ImageRef := BitmapToUIImage(bp).CGImage;
|
|
|
+ if ImageRef <> nil then
|
|
|
+ begin
|
|
|
+ BitmapSize := TSize.Create(CGImageGetWidth(ImageRef), CGImageGetHeight(ImageRef));
|
|
|
+ Bitmap.Clear(TAlphaColorRec.Null);
|
|
|
+ Bitmap.SetSize(BitmapSize.cx, BitmapSize.cy);
|
|
|
+ ColorSpace := CGColorSpaceCreateDeviceRGB;
|
|
|
+ try
|
|
|
+ CtxRef := CGBitmapContextCreate(Bitmap.Bits, Bitmap.Width, Bitmap.Height, 8,
|
|
|
+ Bitmap.Pitch, ColorSpace, kCGImageAlphaPremultipliedLast or kCGBitmapByteOrder32Big);
|
|
|
+ try
|
|
|
+ CGContextDrawImage(CtxRef, CGRectMake(0, 0, Bitmap.Width, Bitmap.Height), ImageRef);
|
|
|
+ finally
|
|
|
+ CGContextRelease(CtxRef);
|
|
|
+ end;
|
|
|
+ Result := True;
|
|
|
+ finally
|
|
|
+ CGColorSpaceRelease(ColorSpace);
|
|
|
+ end;
|
|
|
+ end
|
|
|
+ finally
|
|
|
+ bp.Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TBitmapCodecIOS.Rotate(const AStream: TStream;
|
|
|
+ const Angle: Single): Boolean;
|
|
|
+var bp: TBitmap;
|
|
|
+ DataType: string;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+ DataType := TImageTypeChecker.GetType(AStream);
|
|
|
+ if DataType = SGIFImageExtension then
|
|
|
+ Exit;
|
|
|
+
|
|
|
+ bp:= TBitmap.Create;
|
|
|
+ try
|
|
|
+ AStream.Position := 0;
|
|
|
+ bp.LoadFromStream(AStream);
|
|
|
+ bp.Rotate(Angle);
|
|
|
+ AStream.Size := 0;
|
|
|
+ bp.SaveToStream(AStream, DataType);
|
|
|
+ finally
|
|
|
+ bp.Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+{$ENDIF}
|
|
|
+
|
|
|
end.
|