ImagingJpeg.pas 18 KB

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