|
@@ -6,6 +6,8 @@
|
|
|
|
|
|
interface
|
|
interface
|
|
|
|
|
|
|
|
+{$SCOPEDENUMS ON}
|
|
|
|
+
|
|
uses
|
|
uses
|
|
{$IFDEF FMX}
|
|
{$IFDEF FMX}
|
|
FMX.Graphics,
|
|
FMX.Graphics,
|
|
@@ -97,6 +99,7 @@ type
|
|
/// 用于在线程中代替 Assign ,不用自己调用 Synchronize 了。
|
|
/// 用于在线程中代替 Assign ,不用自己调用 Synchronize 了。
|
|
/// </summary>
|
|
/// </summary>
|
|
procedure SyncAssign(Source: TPersistent);
|
|
procedure SyncAssign(Source: TPersistent);
|
|
|
|
+ procedure SaveToStream(Stream: TStream; const Extension: string; SaveParams: PBitmapCodecSaveParams = nil); overload;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
|
|
@@ -120,6 +123,8 @@ type
|
|
/// </remarks>
|
|
/// </remarks>
|
|
class function LoadThumbnailFromStream(const AStream: TStream; const ASrc, ADest: TRectF;
|
|
class function LoadThumbnailFromStream(const AStream: TStream; const ASrc, ADest: TRectF;
|
|
const UseEmbedded: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
const UseEmbedded: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
|
|
+ class function Rotate(const Extension: string; const Angle: Single; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
|
|
+ class function Rotate(const AStream: TStream; const Angle: Single): Boolean; overload;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
|
|
|
|
@@ -177,9 +182,10 @@ uses
|
|
Androidapi.Helpers,
|
|
Androidapi.Helpers,
|
|
FMX.Helpers.Android,
|
|
FMX.Helpers.Android,
|
|
Androidapi.JNI.GraphicsContentViewText,
|
|
Androidapi.JNI.GraphicsContentViewText,
|
|
|
|
+ Androidapi.JNI.JavaTypes,
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$IFDEF MSWINDOWS}
|
|
{$IFDEF MSWINDOWS}
|
|
- FMX.Canvas.D2D, Winapi.Wincodec, Winapi.Windows,
|
|
|
|
|
|
+ FMX.Canvas.D2D, Winapi.Wincodec, Winapi.Windows, Winapi.ActiveX,
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$ELSE}
|
|
{$ELSE}
|
|
Vcl.Consts,
|
|
Vcl.Consts,
|
|
@@ -235,6 +241,8 @@ type
|
|
{$IFDEF FMX}
|
|
{$IFDEF FMX}
|
|
{$IFDEF ANDROID}
|
|
{$IFDEF ANDROID}
|
|
TBitmapCodecAndroid = class(FMX.Graphics.Android.TBitmapCodecAndroid)
|
|
TBitmapCodecAndroid = class(FMX.Graphics.Android.TBitmapCodecAndroid)
|
|
|
|
+ private const
|
|
|
|
+ DefaultSaveQuality = 75;
|
|
private
|
|
private
|
|
class function IsGIFStream(const Stream: TStream): Boolean;
|
|
class function IsGIFStream(const Stream: TStream): Boolean;
|
|
function LoadMovieFromStreamScaled(const AStream: TStream;
|
|
function LoadMovieFromStreamScaled(const AStream: TStream;
|
|
@@ -254,6 +262,8 @@ type
|
|
const UseEmbedded: Boolean; const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
const UseEmbedded: Boolean; const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
function LoadThumbnailFromStream(const AStream: TStream; const ASrc, ADest: TRectF;
|
|
function LoadThumbnailFromStream(const AStream: TStream; const ASrc, ADest: TRectF;
|
|
const UseEmbedded: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
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;
|
|
end;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$IFDEF MSWINDOWS}
|
|
{$IFDEF MSWINDOWS}
|
|
@@ -267,6 +277,8 @@ type
|
|
const UseEmbedded: Boolean; const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
const UseEmbedded: Boolean; const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
function LoadThumbnailFromStream(const AStream: TStream; const ASrc, ADest: TRectF;
|
|
function LoadThumbnailFromStream(const AStream: TStream; const ASrc, ADest: TRectF;
|
|
const UseEmbedded: Boolean; const Bitmap: TBitmapSurface): Boolean; overload;
|
|
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;
|
|
end;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
@@ -831,6 +843,26 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
+procedure TKngStrBitmapHelper.SaveToStream(Stream: TStream;
|
|
|
|
+ const Extension: string; SaveParams: PBitmapCodecSaveParams);
|
|
|
|
+var
|
|
|
|
+ Surf: TBitmapSurface;
|
|
|
|
+begin
|
|
|
|
+ TMonitor.Enter(Self);
|
|
|
|
+ try
|
|
|
|
+ Surf := TBitmapSurface.Create;
|
|
|
|
+ try
|
|
|
|
+ Surf.Assign(Self);
|
|
|
|
+ if not TBitmapCodecManager.SaveToStream(Stream, Surf, Extension, SaveParams) then
|
|
|
|
+ raise EBitmapSavingFailed.Create(SBitmapSavingFailed);
|
|
|
|
+ finally
|
|
|
|
+ Surf.Free;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ TMonitor.Exit(Self);
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
{$IFDEF FMX}
|
|
{$IFDEF FMX}
|
|
procedure TKngStrBitmapHelper.SyncAssign(Source: TPersistent);
|
|
procedure TKngStrBitmapHelper.SyncAssign(Source: TPersistent);
|
|
begin
|
|
begin
|
|
@@ -1118,6 +1150,58 @@ begin
|
|
end
|
|
end
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
+class function TKngStrBitmapCodecManager.Rotate(const AStream: TStream;
|
|
|
|
+ const Angle: Single): Boolean;
|
|
|
|
+var
|
|
|
|
+ CodecClass: TCustomBitmapCodecClass;
|
|
|
|
+ Codec: TCustomBitmapCodec;
|
|
|
|
+ DataType: String;
|
|
|
|
+begin
|
|
|
|
+ Result := False;
|
|
|
|
+ if (not Assigned(AStream)) or (AStream.Size - AStream.Position <= 0) then
|
|
|
|
+ Exit;
|
|
|
|
+ DataType := TImageTypeChecker.GetType(AStream);
|
|
|
|
+ CodecClass := GuessCodecClass(DataType, TBitmapCodecDescriptorField.Extension);
|
|
|
|
+ if CodecClass <> nil then
|
|
|
|
+ begin
|
|
|
|
+ Codec := CodecClass.Create;
|
|
|
|
+ try
|
|
|
|
+ {$IFDEF ANDROID}
|
|
|
|
+ Result := TBitmapCodecAndroid(Codec).Rotate(AStream, Angle);
|
|
|
|
+ {$ELSE}
|
|
|
|
+ Result := TBitmapCodecWIC(Codec).Rotate(AStream, Angle);
|
|
|
|
+ {$ENDIF}
|
|
|
|
+ finally
|
|
|
|
+ Codec.Free;
|
|
|
|
+ end;
|
|
|
|
+ end
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class function TKngStrBitmapCodecManager.Rotate(const Extension: string;
|
|
|
|
+ const Angle: Single; const Bitmap: TBitmapSurface): Boolean;
|
|
|
|
+var
|
|
|
|
+ CodecClass: TCustomBitmapCodecClass;
|
|
|
|
+ Codec: TCustomBitmapCodec;
|
|
|
|
+begin
|
|
|
|
+ Result := False;
|
|
|
|
+ if Extension = '' then
|
|
|
|
+ Exit;
|
|
|
|
+ CodecClass := GuessCodecClass(Extension, TBitmapCodecDescriptorField.Extension);
|
|
|
|
+ if CodecClass <> nil then
|
|
|
|
+ begin
|
|
|
|
+ Codec := CodecClass.Create;
|
|
|
|
+ try
|
|
|
|
+ {$IFDEF ANDROID}
|
|
|
|
+ Result := TBitmapCodecAndroid(Codec).Rotate(Extension, Angle, Bitmap);
|
|
|
|
+ {$ELSE}
|
|
|
|
+ Result := TBitmapCodecWIC(Codec).Rotate(Extension, Angle, Bitmap);
|
|
|
|
+ {$ENDIF}
|
|
|
|
+ finally
|
|
|
|
+ Codec.Free;
|
|
|
|
+ end;
|
|
|
|
+ end
|
|
|
|
+end;
|
|
|
|
+
|
|
class function TKngStrBitmapCodecManager.LoadThumbnailFromStream(
|
|
class function TKngStrBitmapCodecManager.LoadThumbnailFromStream(
|
|
const AStream: TStream; const AFitWidth, AFitHeight: Single;
|
|
const AStream: TStream; const AFitWidth, AFitHeight: Single;
|
|
const UseEmbedded: Boolean; const AutoCut: Boolean;
|
|
const UseEmbedded: Boolean; const AutoCut: Boolean;
|
|
@@ -1475,6 +1559,114 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
+function TBitmapCodecAndroid.Rotate(const AStream: TStream;
|
|
|
|
+ const Angle: Single): Boolean;
|
|
|
|
+var
|
|
|
|
+ NativeBitmap1, NativeBitmap2: JBitmap;
|
|
|
|
+ SaveFormat: JBitmap_CompressFormat;
|
|
|
|
+ Matrix: JMatrix;
|
|
|
|
+ TempStream: TMemoryStream;
|
|
|
|
+ TempArray: TJavaArray<Byte>;
|
|
|
|
+ LoadOptions: JBitmapFactory_Options;
|
|
|
|
+ SavePosition: Int64;
|
|
|
|
+ OutByteStream: JByteArrayOutputStream;
|
|
|
|
+ ContentBytes: TJavaArray<Byte>;
|
|
|
|
+ DataType: string;
|
|
|
|
+begin
|
|
|
|
+ DataType := TImageTypeChecker.GetType(AStream);
|
|
|
|
+ if DataType = SGIFImageExtension then begin
|
|
|
|
+ Result := False;
|
|
|
|
+ Exit;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ SavePosition := AStream.Position;
|
|
|
|
+ try
|
|
|
|
+ TempStream := TMemoryStream.Create;
|
|
|
|
+ try
|
|
|
|
+ TempStream.CopyFrom(AStream, AStream.Size);
|
|
|
|
+ TempArray := TJavaArray<Byte>.Create(TempStream.Size);
|
|
|
|
+ Move(TempStream.Memory^, TempArray.Data^, TempStream.Size);
|
|
|
|
+ finally
|
|
|
|
+ TempStream.Free;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ LoadOptions := TJBitmapFactory_Options.JavaClass.init;
|
|
|
|
+ LoadOptions.inJustDecodeBounds := False;
|
|
|
|
+ NativeBitmap1 := TJBitmapFactory.JavaClass.decodeByteArray(TempArray, 0, TempArray.Length, LoadOptions);
|
|
|
|
+ TempArray := nil;
|
|
|
|
+ if NativeBitmap1 = nil then
|
|
|
|
+ Exit(False);
|
|
|
|
+ try
|
|
|
|
+ Matrix := TJMatrix.JavaClass.init;
|
|
|
|
+ Matrix.postRotate(Angle);
|
|
|
|
+
|
|
|
|
+ NativeBitmap2 := TJBitmap.JavaClass.createBitmap(NativeBitmap1, 0, 0, NativeBitmap1.getWidth, NativeBitmap1.getHeight, Matrix, True);
|
|
|
|
+ finally
|
|
|
|
+ NativeBitmap1.recycle;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ if NativeBitmap2 = nil then
|
|
|
|
+ Exit(False);
|
|
|
|
+ try
|
|
|
|
+ if SameText(DataType, SPNGImageExtension) then
|
|
|
|
+ SaveFormat := TJBitmap_CompressFormat.JavaClass.PNG
|
|
|
|
+ else
|
|
|
|
+ SaveFormat := TJBitmap_CompressFormat.JavaClass.JPEG;
|
|
|
|
+
|
|
|
|
+ OutByteStream := TJByteArrayOutputStream.JavaClass.init(0);
|
|
|
|
+ Result := NativeBitmap2.compress(SaveFormat, 100, OutByteStream);
|
|
|
|
+ finally
|
|
|
|
+ NativeBitmap2.recycle;
|
|
|
|
+ end;
|
|
|
|
+ if Result and (OutByteStream.size > 0) then begin
|
|
|
|
+ ContentBytes := OutByteStream.toByteArray;
|
|
|
|
+ AStream.Size := 0;
|
|
|
|
+ AStream.WriteBuffer(ContentBytes.Data^, OutByteStream.size);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ Result := Result and (OutByteStream.size > 0);
|
|
|
|
+ finally
|
|
|
|
+ AStream.Position := SavePosition;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TBitmapCodecAndroid.Rotate(const Extension: string; const Angle: Single; const Bitmap: TBitmapSurface): Boolean;
|
|
|
|
+var
|
|
|
|
+ NativeBitmap1, NativeBitmap2: JBitmap;
|
|
|
|
+ SaveFormat: JBitmap_CompressFormat;
|
|
|
|
+ Matrix: JMatrix;
|
|
|
|
+ SaveQuality: Integer;
|
|
|
|
+begin
|
|
|
|
+ if Extension = SGIFImageExtension then begin
|
|
|
|
+ Result := False;
|
|
|
|
+ Exit;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ NativeBitmap1 := TJBitmap.JavaClass.createBitmap(Bitmap.Width, Bitmap.Height, TJBitmap_Config.JavaClass.ARGB_8888);
|
|
|
|
+ if NativeBitmap1 = nil then
|
|
|
|
+ Exit(False);
|
|
|
|
+ try
|
|
|
|
+ Result := SurfaceToJBitmap(Bitmap, NativeBitmap1);
|
|
|
|
+ if not Result then
|
|
|
|
+ Exit;
|
|
|
|
+
|
|
|
|
+ Matrix := TJMatrix.JavaClass.init;
|
|
|
|
+ Matrix.postRotate(Angle);
|
|
|
|
+
|
|
|
|
+ NativeBitmap2 := TJBitmap.JavaClass.createBitmap(NativeBitmap1, 0, 0, Bitmap.Width, Bitmap.Height, Matrix, True);
|
|
|
|
+ finally
|
|
|
|
+ NativeBitmap1.recycle;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ if NativeBitmap2 = nil then
|
|
|
|
+ Exit(False);
|
|
|
|
+ try
|
|
|
|
+ Result := JBitmapToSurface(NativeBitmap2, Bitmap);
|
|
|
|
+ finally
|
|
|
|
+ NativeBitmap2.recycle;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
function TBitmapCodecAndroid.LoadThumbnailFromStream(const AStream: TStream;
|
|
function TBitmapCodecAndroid.LoadThumbnailFromStream(const AStream: TStream;
|
|
const AFitWidth, AFitHeight: Single; const UseEmbedded: Boolean;
|
|
const AFitWidth, AFitHeight: Single; const UseEmbedded: Boolean;
|
|
const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean;
|
|
const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean;
|