ImagingJpeg.pas 18 KB

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