ImagingJpeg.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. {
  2. $Id$
  3. Vampyre Imaging Library
  4. by Marek Mauder
  5. http://imaginglib.sourceforge.net
  6. The contents of this file are used with permission, subject to the Mozilla
  7. Public License Version 1.1 (the "License"); you may not use this file except
  8. in compliance with the License. You may obtain a copy of the License at
  9. http://www.mozilla.org/MPL/MPL-1.1.html
  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 for
  12. the specific language governing rights and limitations under the License.
  13. Alternatively, the contents of this file may be used under the terms of the
  14. GNU Lesser General Public License (the "LGPL License"), in which case the
  15. provisions of the LGPL License are applicable instead of those above.
  16. If you wish to allow use of your version of this file only under the terms
  17. of the LGPL License and not to allow others to use your version of this file
  18. under the MPL, indicate your decision by deleting the provisions above and
  19. replace them with the notice and other provisions required by the LGPL
  20. License. If you do not delete the provisions above, a recipient may use
  21. your version of this file under either the MPL or the LGPL License.
  22. For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
  23. }
  24. { This unit contains image format loader/saver for Jpeg images.}
  25. unit ImagingJpeg;
  26. {$I ImagingOptions.inc}
  27. { You can choose which Pascal JpegLib implementation will be used.
  28. IMJPEGLIB is version bundled with Imaging which works with all supported
  29. compilers and platforms.
  30. PASJPEG is original JpegLib translation or version modified for FPC
  31. (and shipped with it). You can use PASJPEG if this version is already
  32. linked with another part of your program and you don't want to have
  33. two quite large almost the same libraries linked to your exe.
  34. This is the case with Lazarus applications for example.}
  35. {$DEFINE IMJPEGLIB}
  36. { $DEFINE PASJPEG}
  37. { Automatically use FPC's PasJpeg when compiling with Lazarus.}
  38. {$IFDEF LCL}
  39. {$UNDEF IMJPEGLIB}
  40. {$DEFINE PASJPEG}
  41. {$ENDIF}
  42. interface
  43. uses
  44. SysUtils, ImagingTypes, Imaging, ImagingColors,
  45. {$IF Defined(IMJPEGLIB)}
  46. imjpeglib, imjmorecfg, imjcomapi, imjdapimin,
  47. imjdapistd, imjcapimin, imjcapistd, imjdmarker, imjcparam,
  48. {$ELSEIF Defined(PASJPEG)}
  49. jpeglib, jmorecfg, jcomapi, jdapimin,
  50. jdapistd, jcapimin, jcapistd, jdmarker, jcparam,
  51. {$IFEND}
  52. ImagingUtility;
  53. {$IF Defined(FPC) and Defined(PASJPEG)}
  54. { When using FPC's pasjpeg in FPC the channel order is BGR instead of RGB}
  55. {$DEFINE RGBSWAPPED}
  56. {$IFEND}
  57. type
  58. { Class for loading/saving Jpeg images. Supports load/save of
  59. 8 bit grayscale and 24 bit RGB images.}
  60. TJpegFileFormat = class(TImageFileFormat)
  61. private
  62. FGrayScale: Boolean;
  63. protected
  64. FQuality: LongInt;
  65. FProgressive: LongBool;
  66. procedure SetJpegIO(const JpegIO: TIOFunctions); virtual;
  67. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  68. OnlyFirstLevel: Boolean): Boolean; override;
  69. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  70. Index: LongInt): Boolean; override;
  71. procedure ConvertToSupported(var Image: TImageData;
  72. const Info: TImageFormatInfo); override;
  73. public
  74. constructor Create; override;
  75. function TestFormat(Handle: TImagingHandle): Boolean; override;
  76. procedure CheckOptionsValidity; override;
  77. published
  78. { Controls Jpeg save compression quality. It is number in range 1..100.
  79. 1 means small/ugly file, 100 means large/nice file. Accessible trough
  80. ImagingJpegQuality option.}
  81. property Quality: LongInt read FQuality write FQuality;
  82. { If True Jpeg images are saved in progressive format. Accessible trough
  83. ImagingJpegProgressive option.}
  84. property Progressive: LongBool read FProgressive write FProgressive;
  85. end;
  86. implementation
  87. const
  88. SJpegFormatName = 'Joint Photographic Experts Group Image';
  89. SJpegMasks = '*.jpg,*.jpeg,*.jfif,*.jpe,*.jif';
  90. JpegSupportedFormats: TImageFormats = [ifR8G8B8, ifGray8];
  91. JpegDefaultQuality = 90;
  92. JpegDefaultProgressive = False;
  93. const
  94. { Jpeg file identifiers.}
  95. JpegMagic: TChar2 = #$FF#$D8;
  96. JFIFSignature: TChar4 = 'JFIF';
  97. EXIFSignature: TChar4 = 'Exif';
  98. BufferSize = 16384;
  99. type
  100. TJpegContext = record
  101. case Byte of
  102. 0: (common: jpeg_common_struct);
  103. 1: (d: jpeg_decompress_struct);
  104. 2: (c: jpeg_compress_struct);
  105. end;
  106. TSourceMgr = record
  107. Pub: jpeg_source_mgr;
  108. Input: TImagingHandle;
  109. Buffer: JOCTETPTR;
  110. StartOfFile: Boolean;
  111. end;
  112. PSourceMgr = ^TSourceMgr;
  113. TDestMgr = record
  114. Pub: jpeg_destination_mgr;
  115. Output: TImagingHandle;
  116. Buffer: JOCTETPTR;
  117. end;
  118. PDestMgr = ^TDestMgr;
  119. var
  120. JIO: TIOFunctions;
  121. { Intenal unit jpeglib support functions }
  122. procedure JpegError(CurInfo: j_common_ptr);
  123. begin
  124. end;
  125. procedure EmitMessage(CurInfo: j_common_ptr; msg_level: Integer);
  126. begin
  127. end;
  128. procedure OutputMessage(CurInfo: j_common_ptr);
  129. begin
  130. end;
  131. procedure FormatMessage(CurInfo: j_common_ptr; var buffer: string);
  132. begin
  133. end;
  134. procedure ResetErrorMgr(CurInfo: j_common_ptr);
  135. begin
  136. CurInfo^.err^.num_warnings := 0;
  137. CurInfo^.err^.msg_code := 0;
  138. end;
  139. var
  140. JpegErrorRec: jpeg_error_mgr = (
  141. error_exit: JpegError;
  142. emit_message: EmitMessage;
  143. output_message: OutputMessage;
  144. format_message: FormatMessage;
  145. reset_error_mgr: ResetErrorMgr);
  146. procedure ReleaseContext(var jc: TJpegContext);
  147. begin
  148. if jc.common.err = nil then
  149. Exit;
  150. jpeg_destroy(@jc.common);
  151. jpeg_destroy_decompress(@jc.d);
  152. jpeg_destroy_compress(@jc.c);
  153. jc.common.err := nil;
  154. end;
  155. procedure InitSource(cinfo: j_decompress_ptr);
  156. begin
  157. PSourceMgr(cinfo.src).StartOfFile := True;
  158. end;
  159. function FillInputBuffer(cinfo: j_decompress_ptr): Boolean;
  160. var
  161. NBytes: LongInt;
  162. Src: PSourceMgr;
  163. begin
  164. Src := PSourceMgr(cinfo.src);
  165. NBytes := JIO.Read(Src.Input, Src.Buffer, BufferSize);
  166. if NBytes <= 0 then
  167. begin
  168. PChar(Src.Buffer)[0] := #$FF;
  169. PChar(Src.Buffer)[1] := Char(JPEG_EOI);
  170. NBytes := 2;
  171. end;
  172. Src.Pub.next_input_byte := Src.Buffer;
  173. Src.Pub.bytes_in_buffer := NBytes;
  174. Src.StartOfFile := False;
  175. Result := True;
  176. end;
  177. procedure SkipInputData(cinfo: j_decompress_ptr; num_bytes: LongInt);
  178. var
  179. Src: PSourceMgr;
  180. begin
  181. Src := PSourceMgr(cinfo.src);
  182. if num_bytes > 0 then
  183. begin
  184. while num_bytes > Src.Pub.bytes_in_buffer do
  185. begin
  186. Dec(num_bytes, Src.Pub.bytes_in_buffer);
  187. FillInputBuffer(cinfo);
  188. end;
  189. Src.Pub.next_input_byte := @PByteArray(Src.Pub.next_input_byte)[num_bytes];
  190. // Inc(LongInt(Src.Pub.next_input_byte), num_bytes);
  191. Dec(Src.Pub.bytes_in_buffer, num_bytes);
  192. end;
  193. end;
  194. procedure TermSource(cinfo: j_decompress_ptr);
  195. var
  196. Src: PSourceMgr;
  197. begin
  198. Src := PSourceMgr(cinfo.src);
  199. // Move stream position back just after EOI marker so that more that one
  200. // JPEG images can be loaded from one stream
  201. JIO.Seek(Src.Input, -Src.Pub.bytes_in_buffer, smFromCurrent);
  202. end;
  203. procedure JpegStdioSrc(var cinfo: jpeg_decompress_struct; Handle:
  204. TImagingHandle);
  205. var
  206. Src: PSourceMgr;
  207. begin
  208. if cinfo.src = nil then
  209. begin
  210. cinfo.src := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_PERMANENT,
  211. SizeOf(TSourceMgr));
  212. Src := PSourceMgr(cinfo.src);
  213. Src.Buffer := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_PERMANENT,
  214. BufferSize * SizeOf(JOCTET));
  215. end;
  216. Src := PSourceMgr(cinfo.src);
  217. Src.Pub.init_source := InitSource;
  218. Src.Pub.fill_input_buffer := FillInputBuffer;
  219. Src.Pub.skip_input_data := SkipInputData;
  220. Src.Pub.resync_to_restart := jpeg_resync_to_restart;
  221. Src.Pub.term_source := TermSource;
  222. Src.Input := Handle;
  223. Src.Pub.bytes_in_buffer := 0;
  224. Src.Pub.next_input_byte := nil;
  225. end;
  226. procedure InitDest(cinfo: j_compress_ptr);
  227. var
  228. Dest: PDestMgr;
  229. begin
  230. Dest := PDestMgr(cinfo.dest);
  231. Dest.Pub.next_output_byte := Dest.Buffer;
  232. Dest.Pub.free_in_buffer := BufferSize;
  233. end;
  234. function EmptyOutput(cinfo: j_compress_ptr): Boolean;
  235. var
  236. Dest: PDestMgr;
  237. begin
  238. Dest := PDestMgr(cinfo.dest);
  239. JIO.Write(Dest.Output, Dest.Buffer, BufferSize);
  240. Dest.Pub.next_output_byte := Dest.Buffer;
  241. Dest.Pub.free_in_buffer := BufferSize;
  242. Result := True;
  243. end;
  244. procedure TermDest(cinfo: j_compress_ptr);
  245. var
  246. Dest: PDestMgr;
  247. DataCount: LongInt;
  248. begin
  249. Dest := PDestMgr(cinfo.dest);
  250. DataCount := BufferSize - Dest.Pub.free_in_buffer;
  251. if DataCount > 0 then
  252. JIO.Write(Dest.Output, Dest.Buffer, DataCount);
  253. end;
  254. procedure JpegStdioDest(var cinfo: jpeg_compress_struct; Handle:
  255. TImagingHandle);
  256. var
  257. Dest: PDestMgr;
  258. begin
  259. if cinfo.dest = nil then
  260. cinfo.dest := cinfo.mem.alloc_small(j_common_ptr(@cinfo),
  261. JPOOL_PERMANENT, SizeOf(TDestMgr));
  262. Dest := PDestMgr(cinfo.dest);
  263. Dest.Buffer := cinfo.mem.alloc_small(j_common_ptr(@cinfo), JPOOL_IMAGE,
  264. BufferSize * SIZEOF(JOCTET));
  265. Dest.Pub.init_destination := InitDest;
  266. Dest.Pub.empty_output_buffer := EmptyOutput;
  267. Dest.Pub.term_destination := TermDest;
  268. Dest.Output := Handle;
  269. end;
  270. procedure InitDecompressor(Handle: TImagingHandle; var jc: TJpegContext);
  271. begin
  272. FillChar(jc, sizeof(jc), 0);
  273. jc.common.err := @JpegErrorRec;
  274. jpeg_CreateDecompress(@jc.d, JPEG_LIB_VERSION, sizeof(jc.d));
  275. JpegStdioSrc(jc.d, Handle);
  276. jpeg_read_header(@jc.d, True);
  277. jc.d.scale_num := 1;
  278. jc.d.scale_denom := 1;
  279. jc.d.do_block_smoothing := True;
  280. if jc.d.out_color_space = JCS_GRAYSCALE then
  281. begin
  282. jc.d.quantize_colors := True;
  283. jc.d.desired_number_of_colors := 256;
  284. end;
  285. end;
  286. procedure InitCompressor(Handle: TImagingHandle; var jc: TJpegContext;
  287. Saver: TJpegFileFormat);
  288. begin
  289. FillChar(jc, sizeof(jc), 0);
  290. jc.common.err := @JpegErrorRec;
  291. jpeg_CreateCompress(@jc.c, JPEG_LIB_VERSION, sizeof(jc.c));
  292. JpegStdioDest(jc.c, Handle);
  293. jpeg_set_defaults(@jc.c);
  294. jpeg_set_quality(@jc.c, Saver.FQuality, True);
  295. if Saver.FGrayScale then
  296. jpeg_set_colorspace(@jc.c, JCS_GRAYSCALE)
  297. else
  298. jpeg_set_colorspace(@jc.c, JCS_YCbCr);
  299. if Saver.FProgressive then
  300. jpeg_simple_progression(@jc.c);
  301. end;
  302. { TJpegFileFormat class implementation }
  303. constructor TJpegFileFormat.Create;
  304. begin
  305. inherited Create;
  306. FName := SJpegFormatName;
  307. FCanLoad := True;
  308. FCanSave := True;
  309. FIsMultiImageFormat := False;
  310. FSupportedFormats := JpegSupportedFormats;
  311. FQuality := JpegDefaultQuality;
  312. FProgressive := JpegDefaultProgressive;
  313. AddMasks(SJpegMasks);
  314. RegisterOption(ImagingJpegQuality, @FQuality);
  315. RegisterOption(ImagingJpegProgressive, @FProgressive);
  316. end;
  317. procedure TJpegFileFormat.CheckOptionsValidity;
  318. begin
  319. // Check if option values are valid
  320. if not (FQuality in [1..100]) then
  321. FQuality := JpegDefaultQuality;
  322. end;
  323. function TJpegFileFormat.LoadData(Handle: TImagingHandle;
  324. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  325. var
  326. PtrInc, LinesPerCall, LinesRead, I: Integer;
  327. Dest: PByte;
  328. jc: TJpegContext;
  329. Info: TImageFormatInfo;
  330. Col32: PColor32Rec;
  331. {$IFDEF RGBSWAPPED}
  332. Pix: PColor24Rec;
  333. {$ENDIF}
  334. begin
  335. // Copy IO functions to global var used in JpegLib callbacks
  336. SetJpegIO(GetIO);
  337. SetLength(Images, 1);
  338. with JIO, Images[0] do
  339. try
  340. InitDecompressor(Handle, jc);
  341. case jc.d.out_color_space of
  342. JCS_GRAYSCALE: Format := ifGray8;
  343. JCS_RGB: Format := ifR8G8B8;
  344. JCS_CMYK: Format := ifA8R8G8B8;
  345. end;
  346. NewImage(jc.d.image_width, jc.d.image_height, Format, Images[0]);
  347. jpeg_start_decompress(@jc.d);
  348. GetImageFormatInfo(Format, Info);
  349. PtrInc := Width * Info.BytesPerPixel;
  350. LinesPerCall := 1;
  351. Dest := Bits;
  352. while jc.d.output_scanline < jc.d.output_height do
  353. begin
  354. LinesRead := jpeg_read_scanlines(@jc.d, @Dest, LinesPerCall);
  355. {$IFDEF RGBSWAPPED}
  356. if Format = ifR8G8B8 then
  357. begin
  358. Pix := PColor24Rec(Dest);
  359. for I := 0 to Width - 1 do
  360. begin
  361. SwapValues(Pix.R, Pix.B);
  362. Inc(Pix);
  363. end;
  364. end;
  365. {$ENDIF}
  366. Inc(Dest, PtrInc * LinesRead);
  367. end;
  368. if jc.d.out_color_space = JCS_CMYK then
  369. begin
  370. Col32 := Bits;
  371. // Translate from CMYK to RGB
  372. for I := 0 to Width * Height - 1 do
  373. begin
  374. CMYKToRGB(255 - Col32.B, 255 - Col32.G, 255 - Col32.R, 255 - Col32.A,
  375. Col32.R, Col32.G, Col32.B);
  376. Col32.A := 255;
  377. Inc(Col32);
  378. end;
  379. end;
  380. jpeg_finish_output(@jc.d);
  381. jpeg_finish_decompress(@jc.d);
  382. Result := True;
  383. finally
  384. ReleaseContext(jc);
  385. end;
  386. end;
  387. function TJpegFileFormat.SaveData(Handle: TImagingHandle;
  388. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  389. var
  390. PtrInc, LinesWritten: LongInt;
  391. Src, Line: PByte;
  392. jc: TJpegContext;
  393. ImageToSave: TImageData;
  394. Info: TImageFormatInfo;
  395. MustBeFreed: Boolean;
  396. {$IFDEF RGBSWAPPED}
  397. I: LongInt;
  398. Pix: PColor24Rec;
  399. {$ENDIF}
  400. begin
  401. Result := False;
  402. // Copy IO functions to global var used in JpegLib callbacks
  403. SetJpegIO(GetIO);
  404. // Makes image to save compatible with Jpeg saving capabilities
  405. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  406. with JIO, ImageToSave do
  407. try
  408. GetImageFormatInfo(Format, Info);
  409. FGrayScale := Format = ifGray8;
  410. InitCompressor(Handle, jc, Self);
  411. jc.c.image_width := Width;
  412. jc.c.image_height := Height;
  413. if FGrayScale then
  414. begin
  415. jc.c.input_components := 1;
  416. jc.c.in_color_space := JCS_GRAYSCALE;
  417. end
  418. else
  419. begin
  420. jc.c.input_components := 3;
  421. jc.c.in_color_space := JCS_RGB;
  422. end;
  423. PtrInc := Width * Info.BytesPerPixel;
  424. Src := Bits;
  425. {$IFDEF RGBSWAPPED}
  426. GetMem(Line, PtrInc);
  427. {$ENDIF}
  428. jpeg_start_compress(@jc.c, True);
  429. while (jc.c.next_scanline < jc.c.image_height) do
  430. begin
  431. {$IFDEF RGBSWAPPED}
  432. if Format = ifR8G8B8 then
  433. begin
  434. Move(Src^, Line^, PtrInc);
  435. Pix := PColor24Rec(Line);
  436. for I := 0 to Width - 1 do
  437. begin
  438. SwapValues(Pix.R, Pix.B);
  439. Inc(Pix, 1);
  440. end;
  441. end;
  442. {$ELSE}
  443. Line := Src;
  444. {$ENDIF}
  445. LinesWritten := jpeg_write_scanlines(@jc.c, @Line, 1);
  446. Inc(Src, PtrInc * LinesWritten);
  447. end;
  448. jpeg_finish_compress(@jc.c);
  449. Result := True;
  450. finally
  451. ReleaseContext(jc);
  452. if MustBeFreed then
  453. FreeImage(ImageToSave);
  454. {$IFDEF RGBSWAPPED}
  455. FreeMem(Line);
  456. {$ENDIF}
  457. end;
  458. end;
  459. procedure TJpegFileFormat.ConvertToSupported(var Image: TImageData;
  460. const Info: TImageFormatInfo);
  461. begin
  462. if Info.HasGrayChannel then
  463. ConvertImage(Image, ifGray8)
  464. else
  465. ConvertImage(Image, ifR8G8B8);
  466. end;
  467. function TJpegFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  468. var
  469. ReadCount: LongInt;
  470. ID: array[0..9] of Char;
  471. begin
  472. Result := False;
  473. if Handle <> nil then
  474. with GetIO do
  475. begin
  476. FillChar(ID, SizeOf(ID), 0);
  477. ReadCount := Read(Handle, @ID, SizeOf(ID));
  478. Seek(Handle, -ReadCount, smFromCurrent);
  479. Result := (ReadCount = SizeOf(ID)) and
  480. CompareMem(@ID, @JpegMagic, SizeOf(JpegMagic));
  481. end;
  482. end;
  483. procedure TJpegFileFormat.SetJpegIO(const JpegIO: TIOFunctions);
  484. begin
  485. JIO := JpegIO;
  486. end;
  487. initialization
  488. RegisterImageFileFormat(TJpegFileFormat);
  489. {
  490. File Notes:
  491. -- TODOS ----------------------------------------------------
  492. - nothing now
  493. -- 0.25.0 Changes/Bug Fixes ---------------------------------
  494. -- FPC's PasJpeg wasn't really used in last version, fixed.
  495. -- 0.24.1 Changes/Bug Fixes ---------------------------------
  496. - Fixed loading of CMYK jpeg images. Could cause heap corruption
  497. and loaded image looked wrong.
  498. -- 0.23 Changes/Bug Fixes -----------------------------------
  499. - Removed JFIF/EXIF detection from TestFormat. Found JPEGs
  500. with different headers (Lavc) which weren't recognized.
  501. -- 0.21 Changes/Bug Fixes -----------------------------------
  502. - MakeCompatible method moved to base class, put ConvertToSupported here.
  503. GetSupportedFormats removed, it is now set in constructor.
  504. - Made public properties for options registered to SetOption/GetOption
  505. functions.
  506. - Changed extensions to filename masks.
  507. - Changed SaveData, LoadData, and MakeCompatible methods according
  508. to changes in base class in Imaging unit.
  509. - Changes in TestFormat, now reads JFIF and EXIF signatures too.
  510. -- 0.19 Changes/Bug Fixes -----------------------------------
  511. - input position is now set correctly to the end of the image
  512. after loading is done. Loading of sequence of JPEG files stored in
  513. single stream works now
  514. - when loading and saving images in FPC with PASJPEG read and
  515. blue channels are swapped to have the same chanel order as IMJPEGLIB
  516. - you can now choose between IMJPEGLIB and PASJPEG implementations
  517. -- 0.17 Changes/Bug Fixes -----------------------------------
  518. - added SetJpegIO method which is used by JNG image format
  519. }
  520. end.