GR32_Clipboard.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. unit GR32_Clipboard;
  2. (* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1 or LGPL 2.1 with linking exception
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  10. * Software distributed under the License is distributed on an "AS IS" basis,
  11. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * Alternatively, the contents of this file may be used under the terms of the
  16. * Free Pascal modified version of the GNU Lesser General Public License
  17. * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
  18. * of this license are applicable instead of those above.
  19. * Please see the file LICENSE.txt for additional information concerning this
  20. * license.
  21. *
  22. * The Original Code is Clipboard support for Graphics32
  23. *
  24. * The Initial Developer of the Original Code is
  25. * Anders Melander <[email protected]>
  26. *
  27. * Portions created by the Initial Developer are Copyright (C) 2008-2022
  28. * the Initial Developer. All Rights Reserved.
  29. *
  30. * Contributor(s):
  31. *
  32. * ***** END LICENSE BLOCK ***** *)
  33. interface
  34. {$WARN SYMBOL_PLATFORM OFF}
  35. {$include GR32.inc}
  36. uses
  37. Classes,
  38. {$ifdef FPC}
  39. LCLType,
  40. {$endif FPC}
  41. GR32;
  42. type
  43. {$ifdef FPC}
  44. TClipboardFormat = LCLType.TClipboardFormat;
  45. {$else FPC}
  46. TClipboardFormat = Word;
  47. {$endif FPC}
  48. //------------------------------------------------------------------------------
  49. //
  50. // Clipboard functions
  51. //
  52. //------------------------------------------------------------------------------
  53. function CopyBitmap32ToClipboard(const Source: TCustomBitmap32): boolean;
  54. function PasteBitmap32FromClipboard(const Dest: TCustomBitmap32): boolean;
  55. function CanPasteBitmap32: boolean;
  56. function CanPasteBitmap32Alpha: boolean;
  57. //------------------------------------------------------------------------------
  58. //
  59. // Global Memory stream.
  60. // Can be used to read and write data to the clipboard.
  61. //
  62. //------------------------------------------------------------------------------
  63. {$ifndef FPC}
  64. type
  65. TGlobalMemoryStream = class(TCustomMemoryStream)
  66. private
  67. FHandle: HGlobal;
  68. FPointer: pointer;
  69. public
  70. constructor Create(const AHandle: HGlobal);
  71. destructor Destroy; override;
  72. function Write(const Buffer; Count: Longint): Longint; override;
  73. function ReleaseHandle: HGlobal;
  74. property Handle: HGlobal read FHandle;
  75. end;
  76. TOwnedGlobalMemoryStream = class(TGlobalMemoryStream)
  77. public
  78. constructor Create(ASize: NativeUInt);
  79. destructor Destroy; override;
  80. end;
  81. TClipboardMemoryStream = class(TGlobalMemoryStream)
  82. private
  83. FClipboardFormat: TClipboardFormat;
  84. public
  85. constructor Create(AClipboardFormat: TClipboardFormat);
  86. property ClipboardFormat: TClipboardFormat read FClipboardFormat;
  87. end;
  88. {$else FPC}
  89. type
  90. TClipboardMemoryStream = class(TMemoryStream)
  91. private
  92. FClipboardFormat: TClipboardFormat;
  93. protected
  94. public
  95. constructor Create(AClipboardFormat: TClipboardFormat);
  96. property ClipboardFormat: TClipboardFormat read FClipboardFormat;
  97. end;
  98. {$endif FPC}
  99. //------------------------------------------------------------------------------
  100. //------------------------------------------------------------------------------
  101. //------------------------------------------------------------------------------
  102. implementation
  103. uses
  104. {$IFDEF FPC}
  105. LCLIntf,
  106. {$ELSE FPC}
  107. {$ifdef MSWINDOWS}
  108. Windows,
  109. {$ENDIF MSWINDOWS}
  110. {$ENDIF FPC}
  111. {$if defined(FRAMEWORK_VCL)}
  112. Vcl.Graphics,
  113. Vcl.Clipbrd,
  114. {$elseif defined(FRAMEWORK_FMX)}
  115. FMX.Graphics,
  116. FMX.Clipboard,
  117. FMX.Platform,
  118. FMX.Surfaces,
  119. {$elseif defined(FRAMEWORK_LCL)}
  120. Graphics,
  121. Clipbrd,
  122. {$ifend}
  123. SysUtils,
  124. GR32_Resamplers;
  125. {$if defined(FRAMEWORK_FMX)}
  126. type
  127. EClipboardException = EClipboardError;
  128. {$elseif defined(FRAMEWORK_LCL)}
  129. const
  130. CF_DIBV5 = 17;
  131. type
  132. EClipboardException = Exception;
  133. {$ifend}
  134. //------------------------------------------------------------------------------
  135. //
  136. // TGlobalMemoryStream
  137. //
  138. //------------------------------------------------------------------------------
  139. {$if defined(MSWINDOWS) and not defined(FPC)}
  140. constructor TGlobalMemoryStream.Create(const AHandle: HGlobal);
  141. begin
  142. inherited Create;
  143. FHandle := AHandle;
  144. FPointer := GlobalLock(Handle);
  145. if (FPointer = nil) then
  146. RaiseLastOSError;
  147. SetPointer(FPointer, GlobalSize(Handle));
  148. end;
  149. destructor TGlobalMemoryStream.Destroy;
  150. begin
  151. ReleaseHandle;
  152. inherited Destroy;
  153. end;
  154. function TGlobalMemoryStream.ReleaseHandle: HGlobal;
  155. begin
  156. if (FPointer <> nil) then
  157. begin
  158. if (FHandle <> 0) then
  159. GlobalUnlock(FHandle);
  160. FPointer := nil;
  161. end;
  162. Result := FHandle;
  163. FHandle := 0;
  164. end;
  165. function TGlobalMemoryStream.Write(const Buffer; Count: Integer): Longint;
  166. var
  167. Pos: Int64;
  168. begin
  169. Result := 0;
  170. if (Position >= 0) and (Count >= 0) then
  171. begin
  172. Pos := Position + Count;
  173. if Pos > 0 then
  174. begin
  175. if Pos > Size then
  176. begin
  177. FHandle := GlobalReAlloc(FHandle, Pos, GMEM_MOVEABLE);
  178. if (FHandle = 0) then
  179. RaiseLastOSError;
  180. FPointer := GlobalLock(FHandle);
  181. if (FPointer = nil) then
  182. RaiseLastOSError;
  183. SetPointer(FPointer, Pos);
  184. end;
  185. System.Move(Buffer, Pointer(NativeUInt(FPointer) + NativeUInt(Position))^, Count);
  186. Seek(Pos, soFromBeginning);
  187. Result := Count;
  188. end;
  189. end;
  190. end;
  191. //------------------------------------------------------------------------------
  192. //
  193. // TOwnedGlobalMemoryStream
  194. //
  195. //------------------------------------------------------------------------------
  196. constructor TOwnedGlobalMemoryStream.Create(ASize: NativeUInt);
  197. var
  198. Handle: HGlobal;
  199. begin
  200. Handle := GlobalAlloc(GMEM_MOVEABLE, ASize);
  201. if (Handle = 0) then
  202. RaiseLastOSError;
  203. try
  204. inherited Create(Handle);
  205. except
  206. if (Handle <> 0) then
  207. GlobalFree(Handle);
  208. raise;
  209. end;
  210. end;
  211. destructor TOwnedGlobalMemoryStream.Destroy;
  212. var
  213. OwnedHandle: HGlobal;
  214. begin
  215. OwnedHandle := ReleaseHandle;
  216. if (OwnedHandle <> 0) then
  217. GlobalFree(OwnedHandle);
  218. inherited;
  219. end;
  220. {$ifend}
  221. //------------------------------------------------------------------------------
  222. //
  223. // TClipboardMemoryStream
  224. //
  225. //------------------------------------------------------------------------------
  226. {$ifndef FPC}
  227. constructor TClipboardMemoryStream.Create(AClipboardFormat: TClipboardFormat);
  228. var
  229. Handle: HGlobal;
  230. begin
  231. FClipboardFormat := AClipboardFormat;
  232. Handle := GetClipboardData(FClipboardFormat);
  233. if (Handle = 0) then
  234. RaiseLastOSError;
  235. inherited Create(Handle);
  236. end;
  237. {$else FPC}
  238. constructor TClipboardMemoryStream.Create(AClipboardFormat: TClipboardFormat);
  239. begin
  240. inherited Create;
  241. FClipboardFormat := AClipboardFormat;
  242. Clipboard.GetFormat(FClipboardFormat, Self);
  243. Position := 0;
  244. end;
  245. {$endif FPC}
  246. //------------------------------------------------------------------------------
  247. //
  248. // Clipboard functions
  249. //
  250. //------------------------------------------------------------------------------
  251. type
  252. TBitmap32Cracker = class(TCustomBitmap32);
  253. TMemoryStreamCracker = class(TMemoryStream);
  254. function CopyBitmap32ToClipboard(const Source: TCustomBitmap32): boolean;
  255. var
  256. Stream: TStream;
  257. Matte: TBitmap32;
  258. Bitmap: TBitmap;
  259. Size: integer;
  260. {$if defined(FRAMEWORK_FMX)}
  261. ClipboardService: IFMXExtendedClipboardService;
  262. {$ifend}
  263. begin
  264. Result := True;
  265. (*
  266. ** We place the following data on the clipboard:
  267. **
  268. ** - CF_BITMAP
  269. ** This is the source bitmap rendered onto a white background.
  270. ** Transparency is not retained.
  271. ** For use by applications that doesn't support Alpha.
  272. **
  273. ** - CF_DIBV5
  274. ** A 32 bit DIB with alpha. This alone can be used to recreate the original
  275. ** 32 bit bitmap, including alpha.
  276. ** This format provides round trip support.
  277. **
  278. ** Since Windows can synthesize between any of CF_DIB, CF_BITMAP and CF_DIBV5
  279. ** theoretically we could just supply the most capable format (CF_DIBV5) and
  280. ** let Windows supply the others. Unfortunately we need to supply both CF_DIBV5
  281. ** and CF_BITMAP/CF_DIB in order to work around various Windows bugs:
  282. **
  283. ** - When the clipboard synthesizes CF_DIBV5 from CF_DIB it uses BI_BITFIELDS.
  284. ** However, if the clipboard synthesizes CF_DIB from CF_DIBV5 with
  285. ** BI_BITFIELDS compression, the clipboard apparently forgets to take the
  286. ** extra 3 mask DWORDs into account which messes up the resulting DIB.
  287. **
  288. ** - When the clipboard synthesizes CF_DIB or CF_BITMAP from CF_DIBV5 it
  289. ** appears to require 68 extra bytes after the bitmap header.
  290. ** Inserting these 68 bytes would fix that but would also make the bitmap
  291. ** stream invalid for everything else.
  292. ** FWIW, 68 = SizeOf(BITMAPV4HEADER)-SizeOf(BITMAPINFOHEADER)...
  293. **
  294. ** As a bonus we get to control the background color of the CF_DIB/CF_BITMAP
  295. ** bitmap instead of the black one Windows would use.
  296. *)
  297. {$if defined(FRAMEWORK_FMX)}
  298. if (not TPlatformServices.Current.SupportsPlatformService(IFMXExtendedClipboardService, ClipboardService)) then
  299. exit;
  300. {$ifend}
  301. {$if not defined(FRAMEWORK_FMX)}
  302. Clipboard.Open;
  303. {$ifend}
  304. try
  305. if (Source.Empty) then
  306. exit(False);
  307. // Render the bitmap onto a white background and copy it as CF_BITMAP.
  308. // Note: In some older versions of Windows it appears that the
  309. // clipboard gives priority to the synthesized CF_BITMAP over the
  310. // explicit CF_BITMAP.
  311. Bitmap := TBitmap.Create;
  312. try
  313. Matte := TBitmap32.Create;
  314. try
  315. Matte.SetSize(Source.Width, Source.Height);
  316. Matte.Clear(clWhite32);
  317. BlockTransfer(Matte, 0, 0, Matte.ClipRect, Source, Source.BoundsRect, dmBlend);
  318. Bitmap.Assign(Matte);
  319. finally
  320. Matte.Free;
  321. end;
  322. {$if not defined(FRAMEWORK_FMX)}
  323. Clipboard.Assign(Bitmap);
  324. {$else}
  325. ClipboardService.SetClipboard(Bitmap);
  326. {$ifend}
  327. finally
  328. Bitmap.Free;
  329. end;
  330. // Preallocate the minimum that we might need and no more.
  331. Size := 124 {124=SizeOf(TBitmapV5Header)} + Source.Width * Source.Height * SizeOf(DWORD);
  332. {$if defined(FRAMEWORK_VCL)}
  333. // Copy the unaltered image as CF_DIBV5
  334. Stream := TOwnedGlobalMemoryStream.Create(Size);
  335. {$else}
  336. Stream := TMemoryStream.Create;
  337. {$ifend}
  338. try
  339. {$if not defined(FRAMEWORK_VCL)}
  340. TMemoryStreamCracker(Stream).Capacity := Size;
  341. {$ifend}
  342. // The clipboard needs a v5 DIB *without* a color table.
  343. // Note that Firefox, at the time of writing, expects a color table for v4 and v5 DIBs
  344. // so it will not be able to correctly read what we put on the clipboard. Our position
  345. // is that this is a bug in Firefox.
  346. //
  347. // See:
  348. // - https://bugzilla.mozilla.org/show_bug.cgi?id=1866655
  349. // - https://forums.getpaint.net/topic/124628-1-px-line-on-top-of-every-image-pasted-into-firefox-from-paintnet/
  350. // - https://github.com/graphics32/graphics32/issues/257
  351. //
  352. // See also:
  353. // - https://github.com/chromium/chromium/commit/e6f56636f365bdb210874bdbe63272f783792c7d
  354. //
  355. // A possible workaround for this problem is to *also* place the bitmap as a PNG on
  356. // the clipboard. It doesn't help with Firefox but apparently some other applications
  357. // give priority to the PNG format when reading from the clipboard.
  358. //
  359. TBitmap32Cracker(Source).SaveToDIBStream(Stream, False, TCustomBitmap32.TInfoHeaderVersion.InfoHeaderVersion5, False);
  360. {$if defined(FRAMEWORK_VCL)}
  361. Clipboard.SetAsHandle(CF_DIBV5, TGlobalMemoryStream(Stream).ReleaseHandle);
  362. {$elseif defined(FRAMEWORK_FMX)}
  363. ClipboardService.SetCustomFormat('CF_DIBV5', Stream);
  364. {$else}
  365. Clipboard.AddFormat(CF_DIBV5, Stream);
  366. {$ifend}
  367. finally
  368. Stream.Free;
  369. end;
  370. finally
  371. {$if not defined(FRAMEWORK_FMX)}
  372. Clipboard.Close;
  373. {$ifend}
  374. end;
  375. end;
  376. //------------------------------------------------------------------------------
  377. function PasteBitmap32FromClipboard(const Dest: TCustomBitmap32): boolean;
  378. var
  379. Stream: TStream;
  380. Bitmap: TBitmap;
  381. {$if defined(FRAMEWORK_FMX)}
  382. ClipboardService: IFMXExtendedClipboardService;
  383. BitmapSurface: TBitmapSurface;
  384. {$ifend}
  385. begin
  386. Result := False;
  387. {$if defined(FRAMEWORK_FMX)}
  388. if (not TPlatformServices.Current.SupportsPlatformService(IFMXExtendedClipboardService, ClipboardService)) then
  389. exit;
  390. {$ifend}
  391. {$if not defined(FRAMEWORK_FMX)}
  392. if (Clipboard.HasFormat(CF_DIBV5)) then
  393. {$else}
  394. if (ClipboardService.HasCustomFormat('CF_DIBV5')) then
  395. {$ifend}
  396. begin
  397. Dest.BeginUpdate;
  398. try
  399. {$if defined(FRAMEWORK_VCL)}
  400. Clipboard.Open;
  401. {$ifend}
  402. try
  403. {$if not defined(FRAMEWORK_FMX)}
  404. Stream := TClipboardMemoryStream.Create(CF_DIBV5);
  405. {$else}
  406. Stream := TMemoryStream.Create;
  407. {$ifend}
  408. try
  409. {$if defined(FRAMEWORK_FMX)}
  410. ClipboardService.GetCustomFormat('CF_DIBV5', Stream);
  411. Stream.Position := 0;
  412. {$ifend}
  413. Result := TBitmap32Cracker(Dest).LoadFromDIBStream(Stream, Stream.Size);
  414. finally
  415. Stream.Free;
  416. end;
  417. finally
  418. {$if defined(FRAMEWORK_VCL)}
  419. Clipboard.Close;
  420. {$ifend}
  421. end;
  422. finally
  423. Dest.EndUpdate;
  424. end;
  425. Dest.Changed;
  426. end;
  427. // There's no need to fall back to CF_DIB since the clipboard will
  428. //synthesize CF_DIBV5 from CF_DIB.
  429. {$if not defined(FRAMEWORK_FMX)}
  430. if (not Result) and (Clipboard.HasFormat(CF_BITMAP)) then
  431. {$else}
  432. if (not Result) and (ClipboardService.HasImage) then
  433. {$ifend}
  434. begin
  435. // Fall back to CF_BITMAP format.
  436. // Note: We must do an explicit assign to a bitmap or we risk that the
  437. // clipboard retrives the data in some other compatible format.
  438. // E.g. if the clipboard contains CF_METAFILE and CF_BITMAP and we do a
  439. // TBitmap32.Assign(Clipboard), then we end grabbing the CF_METAFILE data
  440. // leading to a rasterized copy of a metafile capture of a bitmap... Ugh!
  441. Dest.BeginUpdate;
  442. try
  443. Bitmap := TBitmap.Create;
  444. try
  445. {$if not defined(FRAMEWORK_FMX)}
  446. Bitmap.Assign(Clipboard);
  447. {$else}
  448. BitmapSurface := ClipboardService.GetImage;
  449. Bitmap.Assign(BitmapSurface);
  450. {$ifend}
  451. Dest.Assign(Bitmap);
  452. finally
  453. Bitmap.Free;
  454. end;
  455. finally
  456. Dest.EndUpdate;
  457. end;
  458. Dest.Changed;
  459. Result := True;
  460. end;
  461. end;
  462. //------------------------------------------------------------------------------
  463. function CanPasteBitmap32: boolean;
  464. {$if defined(FRAMEWORK_FMX)}
  465. var
  466. ClipboardService: IFMXExtendedClipboardService;
  467. {$ifend}
  468. begin
  469. try
  470. {$if not defined(FRAMEWORK_FMX)}
  471. Result:= Clipboard.HasFormat(CF_BITMAP) or Clipboard.HasFormat(CF_DIBV5);
  472. {$else}
  473. Result := (TPlatformServices.Current.SupportsPlatformService(IFMXExtendedClipboardService, ClipboardService)) and
  474. (ClipboardService.HasImage) or (ClipboardService.HasCustomFormat('CF_DIBV5'));
  475. {$ifend}
  476. except
  477. on E: EClipboardException do
  478. Result := False; // Something else has the clipboard open
  479. end;
  480. end;
  481. //------------------------------------------------------------------------------
  482. function CanPasteBitmap32Alpha: boolean;
  483. {$if defined(FRAMEWORK_FMX)}
  484. var
  485. ClipboardService: IFMXExtendedClipboardService;
  486. {$ifend}
  487. begin
  488. try
  489. {$if not defined(FRAMEWORK_FMX)}
  490. Result:= Clipboard.HasFormat(CF_DIBV5);
  491. {$else}
  492. Result := (TPlatformServices.Current.SupportsPlatformService(IFMXExtendedClipboardService, ClipboardService)) and
  493. (ClipboardService.HasCustomFormat('CF_DIBV5'));
  494. {$ifend}
  495. except
  496. on E: EClipboardException do
  497. Result := False; // Something else has the clipboard open
  498. end;
  499. end;
  500. //------------------------------------------------------------------------------
  501. end.