Browse Source

add Rotate & SaveToStream

KngStr 3 years ago
parent
commit
56777f1016
1 changed files with 193 additions and 1 deletions
  1. 193 1
      Source/ksBitmapHelper.pas

+ 193 - 1
Source/ksBitmapHelper.pas

@@ -6,6 +6,8 @@
 
 interface
 
+{$SCOPEDENUMS ON}
+
 uses
 {$IFDEF FMX}
   FMX.Graphics,
@@ -97,6 +99,7 @@ type
     /// 用于在线程中代替 Assign ,不用自己调用 Synchronize 了。
     /// </summary>
     procedure SyncAssign(Source: TPersistent);
+    procedure SaveToStream(Stream: TStream; const Extension: string; SaveParams: PBitmapCodecSaveParams = nil); overload;
 {$ENDIF}
   end;
 
@@ -120,6 +123,8 @@ type
     /// </remarks>
     class function LoadThumbnailFromStream(const AStream: TStream; const ASrc, ADest: TRectF;
       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;
 {$ENDIF}
 
@@ -177,9 +182,10 @@ uses
   Androidapi.Helpers,
   FMX.Helpers.Android,
   Androidapi.JNI.GraphicsContentViewText,
+  Androidapi.JNI.JavaTypes,
   {$ENDIF}
   {$IFDEF MSWINDOWS}
-  FMX.Canvas.D2D, Winapi.Wincodec,  Winapi.Windows,
+  FMX.Canvas.D2D, Winapi.Wincodec, Winapi.Windows, Winapi.ActiveX,
   {$ENDIF}
 {$ELSE}
   Vcl.Consts,
@@ -235,6 +241,8 @@ type
 {$IFDEF FMX}
   {$IFDEF ANDROID}
   TBitmapCodecAndroid = class(FMX.Graphics.Android.TBitmapCodecAndroid)
+  private const
+    DefaultSaveQuality = 75;
   private
     class function IsGIFStream(const Stream: TStream): Boolean;
     function LoadMovieFromStreamScaled(const AStream: TStream;
@@ -254,6 +262,8 @@ type
       const UseEmbedded: Boolean; const AutoCut: 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}
@@ -267,6 +277,8 @@ type
       const UseEmbedded: Boolean; const AutoCut: 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}
 {$ENDIF}
@@ -831,6 +843,26 @@ begin
   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}
 procedure TKngStrBitmapHelper.SyncAssign(Source: TPersistent);
 begin
@@ -1118,6 +1150,58 @@ begin
   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(
   const AStream: TStream; const AFitWidth, AFitHeight: Single;
   const UseEmbedded: Boolean; const AutoCut: Boolean;
@@ -1475,6 +1559,114 @@ begin
   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;
   const AFitWidth, AFitHeight: Single; const UseEmbedded: Boolean;
   const AutoCut: Boolean; const Bitmap: TBitmapSurface): Boolean;