ImagingTarga.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  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 Targa images.}
  25. unit ImagingTarga;
  26. {$I ImagingOptions.inc}
  27. interface
  28. uses
  29. ImagingTypes, Imaging, ImagingFormats, ImagingUtility;
  30. type
  31. { Class for loading and saving Truevision Targa images.
  32. It can load/save 8bit indexed or grayscale, 16 bit RGB or grayscale,
  33. 24 bit RGB and 32 bit ARGB images with or without RLE compression.}
  34. TTargaFileFormat = class(TImageFileFormat)
  35. protected
  36. FUseRLE: LongBool;
  37. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  38. OnlyFirstLevel: Boolean): Boolean; override;
  39. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  40. Index: LongInt): Boolean; override;
  41. procedure ConvertToSupported(var Image: TImageData;
  42. const Info: TImageFormatInfo); override;
  43. public
  44. constructor Create; override;
  45. function TestFormat(Handle: TImagingHandle): Boolean; override;
  46. published
  47. { Controls that RLE compression is used during saving. Accessible trough
  48. ImagingTargaRLE option.}
  49. property UseRLE: LongBool read FUseRLE write FUseRLE;
  50. end;
  51. implementation
  52. const
  53. STargaFormatName = 'Truevision Targa Image';
  54. STargaMasks = '*.tga';
  55. TargaSupportedFormats: TImageFormats = [ifIndex8, ifGray8, ifA1R5G5B5,
  56. ifR8G8B8, ifA8R8G8B8];
  57. TargaDefaultRLE = False;
  58. const
  59. STargaSignature = 'TRUEVISION-XFILE';
  60. type
  61. { Targa file header.}
  62. TTargaHeader = packed record
  63. IDLength: Byte;
  64. ColorMapType: Byte;
  65. ImageType: Byte;
  66. ColorMapOff: Word;
  67. ColorMapLength: Word;
  68. ColorEntrySize: Byte;
  69. XOrg: SmallInt;
  70. YOrg: SmallInt;
  71. Width: SmallInt;
  72. Height: SmallInt;
  73. PixelSize: Byte;
  74. Desc: Byte;
  75. end;
  76. { Footer at the end of TGA file.}
  77. TTargaFooter = packed record
  78. ExtOff: LongWord; // Extension Area Offset
  79. DevDirOff: LongWord; // Developer Directory Offset
  80. Signature: TChar16; // TRUEVISION-XFILE
  81. Reserved: Byte; // ASCII period '.'
  82. NullChar: Byte; // 0
  83. end;
  84. { TTargaFileFormat class implementation }
  85. constructor TTargaFileFormat.Create;
  86. begin
  87. inherited Create;
  88. FName := STargaFormatName;
  89. FCanLoad := True;
  90. FCanSave := True;
  91. FIsMultiImageFormat := False;
  92. FSupportedFormats := TargaSupportedFormats;
  93. FUseRLE := TargaDefaultRLE;
  94. AddMasks(STargaMasks);
  95. RegisterOption(ImagingTargaRLE, @FUseRLE);
  96. end;
  97. function TTargaFileFormat.LoadData(Handle: TImagingHandle;
  98. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  99. var
  100. Hdr: TTargaHeader;
  101. Foo: TTargaFooter;
  102. FooterFound, ExtFound: Boolean;
  103. I, PSize, PalSize: LongWord;
  104. Pal: Pointer;
  105. FmtInfo: TImageFormatInfo;
  106. WordValue: Word;
  107. procedure LoadRLE;
  108. var
  109. I, CPixel, Cnt: LongInt;
  110. Bpp, Rle: Byte;
  111. Buffer, Dest, Src: PByte;
  112. BufSize: LongInt;
  113. begin
  114. with GetIO, Images[0] do
  115. begin
  116. // Alocates buffer large enough to hold the worst case
  117. // RLE compressed data and reads then from input
  118. BufSize := Width * Height * FmtInfo.BytesPerPixel;
  119. BufSize := BufSize + BufSize div 2 + 1;
  120. GetMem(Buffer, BufSize);
  121. Src := Buffer;
  122. Dest := Bits;
  123. BufSize := Read(Handle, Buffer, BufSize);
  124. Cnt := Width * Height;
  125. Bpp := FmtInfo.BytesPerPixel;
  126. CPixel := 0;
  127. while CPixel < Cnt do
  128. begin
  129. Rle := Src^;
  130. Inc(Src);
  131. if Rle < 128 then
  132. begin
  133. // Process uncompressed pixel
  134. Rle := Rle + 1;
  135. CPixel := CPixel + Rle;
  136. for I := 0 to Rle - 1 do
  137. begin
  138. // Copy pixel from src to dest
  139. case Bpp of
  140. 1: Dest^ := Src^;
  141. 2: PWord(Dest)^ := PWord(Src)^;
  142. 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
  143. 4: PLongWord(Dest)^ := PLongWord(Src)^;
  144. end;
  145. Inc(Src, Bpp);
  146. Inc(Dest, Bpp);
  147. end;
  148. end
  149. else
  150. begin
  151. // Process compressed pixels
  152. Rle := Rle - 127;
  153. CPixel := CPixel + Rle;
  154. // Copy one pixel from src to dest (many times there)
  155. for I := 0 to Rle - 1 do
  156. begin
  157. case Bpp of
  158. 1: Dest^ := Src^;
  159. 2: PWord(Dest)^ := PWord(Src)^;
  160. 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
  161. 4: PLongWord(Dest)^ := PLongWord(Src)^;
  162. end;
  163. Inc(Dest, Bpp);
  164. end;
  165. Inc(Src, Bpp);
  166. end;
  167. end;
  168. // set position in source to real end of compressed data
  169. Seek(Handle, -(BufSize - LongInt(LongWord(Src) - LongWord(Buffer))),
  170. smFromCurrent);
  171. FreeMem(Buffer);
  172. end;
  173. end;
  174. begin
  175. SetLength(Images, 1);
  176. with GetIO, Images[0] do
  177. begin
  178. // Read targa header
  179. Read(Handle, @Hdr, SizeOf(Hdr));
  180. // Skip image ID info
  181. Seek(Handle, Hdr.IDLength, smFromCurrent);
  182. // Determine image format
  183. Format := ifUnknown;
  184. case Hdr.ImageType of
  185. 1, 9: Format := ifIndex8;
  186. 2, 10: case Hdr.PixelSize of
  187. 15: Format := ifX1R5G5B5;
  188. 16: Format := ifA1R5G5B5;
  189. 24: Format := ifR8G8B8;
  190. 32: Format := ifA8R8G8B8;
  191. end;
  192. 3, 11: Format := ifGray8;
  193. end;
  194. // Format was not assigned by previous testing (it should be in
  195. // well formed targas), so formats which reflects bit dept are selected
  196. if Format = ifUnknown then
  197. case Hdr.PixelSize of
  198. 8: Format := ifGray8;
  199. 15: Format := ifX1R5G5B5;
  200. 16: Format := ifA1R5G5B5;
  201. 24: Format := ifR8G8B8;
  202. 32: Format := ifA8R8G8B8;
  203. end;
  204. NewImage(Hdr.Width, Hdr.Height, Format, Images[0]);
  205. FmtInfo := GetFormatInfo(Format);
  206. if (Hdr.ColorMapType = 1) and (Hdr.ImageType in [1, 9]) then
  207. begin
  208. // Read palette
  209. PSize := Hdr.ColorMapLength * (Hdr.ColorEntrySize shr 3);
  210. GetMem(Pal, PSize);
  211. try
  212. Read(Handle, Pal, PSize);
  213. // Process palette
  214. PalSize := Iff(Hdr.ColorMapLength > FmtInfo.PaletteEntries,
  215. FmtInfo.PaletteEntries, Hdr.ColorMapLength);
  216. for I := 0 to PalSize - 1 do
  217. case Hdr.ColorEntrySize of
  218. 24:
  219. with Palette[I] do
  220. begin
  221. A := $FF;
  222. R := PPalette24(Pal)[I].R;
  223. G := PPalette24(Pal)[I].G;
  224. B := PPalette24(Pal)[I].B;
  225. end;
  226. // I've never seen tga with these palettes so they are untested
  227. 16:
  228. with Palette[I] do
  229. begin
  230. A := (PWordArray(Pal)[I] and $8000) shr 12;
  231. R := (PWordArray(Pal)[I] and $FC00) shr 7;
  232. G := (PWordArray(Pal)[I] and $03E0) shr 2;
  233. B := (PWordArray(Pal)[I] and $001F) shl 3;
  234. end;
  235. 32:
  236. with Palette[I] do
  237. begin
  238. A := PPalette32(Pal)[I].A;
  239. R := PPalette32(Pal)[I].R;
  240. G := PPalette32(Pal)[I].G;
  241. B := PPalette32(Pal)[I].B;
  242. end;
  243. end;
  244. finally
  245. FreeMemNil(Pal);
  246. end;
  247. end;
  248. case Hdr.ImageType of
  249. 0, 1, 2, 3:
  250. // Load uncompressed mode images
  251. Read(Handle, Bits, Size);
  252. 9, 10, 11:
  253. // Load RLE compressed mode images
  254. LoadRLE;
  255. end;
  256. // Check if there is alpha channel present in A1R5GB5 images, if it is not
  257. // change format to X1R5G5B5
  258. if Format = ifA1R5G5B5 then
  259. begin
  260. if not Has16BitImageAlpha(Width * Height, Bits) then
  261. Format := ifX1R5G5B5;
  262. end;
  263. // We must find true end of file and set input' position to it
  264. // paint programs appends extra info at the end of Targas
  265. // some of them multiple times (PSP Pro 8)
  266. repeat
  267. ExtFound := False;
  268. FooterFound := False;
  269. if Read(Handle, @WordValue, 2) = 2 then
  270. begin
  271. // 495 = size of Extension Area
  272. if WordValue = 495 then
  273. begin
  274. Seek(Handle, 493, smFromCurrent);
  275. ExtFound := True;
  276. end
  277. else
  278. Seek(Handle, -2, smFromCurrent);
  279. end;
  280. if Read(Handle, @Foo, SizeOf(Foo)) = SizeOf(Foo) then
  281. begin
  282. if Foo.Signature = STargaSignature then
  283. FooterFound := True
  284. else
  285. Seek(Handle, -SizeOf(Foo), smFromCurrent);
  286. end;
  287. until (not ExtFound) and (not FooterFound);
  288. // Some editors save targas flipped
  289. if Hdr.Desc < 31 then
  290. FlipImage(Images[0]);
  291. Result := True;
  292. end;
  293. end;
  294. function TTargaFileFormat.SaveData(Handle: TImagingHandle;
  295. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  296. var
  297. I: LongInt;
  298. Hdr: TTargaHeader;
  299. FmtInfo: TImageFormatInfo;
  300. Pal: PPalette24;
  301. ImageToSave: TImageData;
  302. MustBeFreed: Boolean;
  303. procedure SaveRLE;
  304. var
  305. Dest: PByte;
  306. WidthBytes, Written, I, Total, DestSize: LongInt;
  307. function CountDiff(Data: PByte; Bpp, PixelCount: Longint): LongInt;
  308. var
  309. Pixel: LongWord;
  310. NextPixel: LongWord;
  311. N: LongInt;
  312. begin
  313. N := 0;
  314. Pixel := 0;
  315. NextPixel := 0;
  316. if PixelCount = 1 then
  317. begin
  318. Result := PixelCount;
  319. Exit;
  320. end;
  321. case Bpp of
  322. 1: Pixel := Data^;
  323. 2: Pixel := PWord(Data)^;
  324. 3: PColor24Rec(@Pixel)^ := PColor24Rec(Data)^;
  325. 4: Pixel := PLongWord(Data)^;
  326. end;
  327. while PixelCount > 1 do
  328. begin
  329. Inc(Data, Bpp);
  330. case Bpp of
  331. 1: NextPixel := Data^;
  332. 2: NextPixel := PWord(Data)^;
  333. 3: PColor24Rec(@NextPixel)^ := PColor24Rec(Data)^;
  334. 4: NextPixel := PLongWord(Data)^;
  335. end;
  336. if NextPixel = Pixel then
  337. Break;
  338. Pixel := NextPixel;
  339. N := N + 1;
  340. PixelCount := PixelCount - 1;
  341. end;
  342. if NextPixel = Pixel then
  343. Result := N
  344. else
  345. Result := N + 1;
  346. end;
  347. function CountSame(Data: PByte; Bpp, PixelCount: LongInt): LongInt;
  348. var
  349. Pixel: LongWord;
  350. NextPixel: LongWord;
  351. N: LongInt;
  352. begin
  353. N := 1;
  354. Pixel := 0;
  355. NextPixel := 0;
  356. case Bpp of
  357. 1: Pixel := Data^;
  358. 2: Pixel := PWord(Data)^;
  359. 3: PColor24Rec(@Pixel)^ := PColor24Rec(Data)^;
  360. 4: Pixel := PLongWord(Data)^;
  361. end;
  362. PixelCount := PixelCount - 1;
  363. while PixelCount > 0 do
  364. begin
  365. Inc(Data, Bpp);
  366. case Bpp of
  367. 1: NextPixel := Data^;
  368. 2: NextPixel := PWord(Data)^;
  369. 3: PColor24Rec(@NextPixel)^ := PColor24Rec(Data)^;
  370. 4: NextPixel := PLongWord(Data)^;
  371. end;
  372. if NextPixel <> Pixel then
  373. Break;
  374. N := N + 1;
  375. PixelCount := PixelCount - 1;
  376. end;
  377. Result := N;
  378. end;
  379. procedure RleCompressLine(Data: PByte; PixelCount, Bpp: LongInt; Dest:
  380. PByte; var Written: LongInt);
  381. const
  382. MaxRun = 128;
  383. var
  384. DiffCount: LongInt;
  385. SameCount: LongInt;
  386. RleBufSize: LongInt;
  387. begin
  388. RleBufSize := 0;
  389. while PixelCount > 0 do
  390. begin
  391. DiffCount := CountDiff(Data, Bpp, PixelCount);
  392. SameCount := CountSame(Data, Bpp, PixelCount);
  393. if (DiffCount > MaxRun) then
  394. DiffCount := MaxRun;
  395. if (SameCount > MaxRun) then
  396. SameCount := MaxRun;
  397. if (DiffCount > 0) then
  398. begin
  399. Dest^ := Byte(DiffCount - 1);
  400. Inc(Dest);
  401. PixelCount := PixelCount - DiffCount;
  402. RleBufSize := RleBufSize + (DiffCount * Bpp) + 1;
  403. Move(Data^, Dest^, DiffCount * Bpp);
  404. Inc(Data, DiffCount * Bpp);
  405. Inc(Dest, DiffCount * Bpp);
  406. end;
  407. if SameCount > 1 then
  408. begin
  409. Dest^ := Byte((SameCount - 1) or $80);
  410. Inc(Dest);
  411. PixelCount := PixelCount - SameCount;
  412. RleBufSize := RleBufSize + Bpp + 1;
  413. Inc(Data, (SameCount - 1) * Bpp);
  414. case Bpp of
  415. 1: Dest^ := Data^;
  416. 2: PWord(Dest)^ := PWord(Data)^;
  417. 3: PColor24Rec(Dest)^ := PColor24Rec(Data)^;
  418. 4: PLongWord(Dest)^ := PLongWord(Data)^;
  419. end;
  420. Inc(Data, Bpp);
  421. Inc(Dest, Bpp);
  422. end;
  423. end;
  424. Written := RleBufSize;
  425. end;
  426. begin
  427. with ImageToSave do
  428. begin
  429. // Allocate enough space to hold the worst case compression
  430. // result and then compress source's scanlines
  431. WidthBytes := Width * FmtInfo.BytesPerPixel;
  432. DestSize := WidthBytes * Height;
  433. DestSize := DestSize + DestSize div 2 + 1;
  434. GetMem(Dest, DestSize);
  435. Total := 0;
  436. try
  437. for I := 0 to Height - 1 do
  438. begin
  439. RleCompressLine(@PByteArray(Bits)[I * WidthBytes], Width,
  440. FmtInfo.BytesPerPixel, @PByteArray(Dest)[Total], Written);
  441. Total := Total + Written;
  442. end;
  443. GetIO.Write(Handle, Dest, Total);
  444. finally
  445. FreeMem(Dest);
  446. end;
  447. end;
  448. end;
  449. begin
  450. Result := False;
  451. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  452. with GetIO, ImageToSave do
  453. try
  454. FmtInfo := GetFormatInfo(Format);
  455. // Fill targa header
  456. FillChar(Hdr, SizeOf(Hdr), 0);
  457. Hdr.IDLength := 0;
  458. Hdr.ColorMapType := Iff(FmtInfo.PaletteEntries > 0, 1, 0);
  459. Hdr.Width := Width;
  460. Hdr.Height := Height;
  461. Hdr.PixelSize := FmtInfo.BytesPerPixel * 8;
  462. Hdr.ColorMapLength := FmtInfo.PaletteEntries;
  463. Hdr.ColorEntrySize := Iff(FmtInfo.PaletteEntries > 0, 24, 0);
  464. Hdr.ColorMapOff := 0;
  465. // This indicates that targa is stored in top-left format
  466. // as our images -> no flipping is needed.
  467. Hdr.Desc := 32;
  468. // Set alpha channel size in descriptor (mostly ignored by other software though)
  469. if Format = ifA8R8G8B8 then
  470. Hdr.Desc := Hdr.Desc or 8
  471. else if Format = ifA1R5G5B5 then
  472. Hdr.Desc := Hdr.Desc or 1;
  473. // Choose image type
  474. if FmtInfo.IsIndexed then
  475. Hdr.ImageType := Iff(FUseRLE, 9, 1)
  476. else
  477. if FmtInfo.HasGrayChannel then
  478. Hdr.ImageType := Iff(FUseRLE, 11, 3)
  479. else
  480. Hdr.ImageType := Iff(FUseRLE, 10, 2);
  481. Write(Handle, @Hdr, SizeOf(Hdr));
  482. // Write palette
  483. if FmtInfo.PaletteEntries > 0 then
  484. begin
  485. GetMem(Pal, FmtInfo.PaletteEntries * SizeOf(TColor24Rec));
  486. try
  487. for I := 0 to FmtInfo.PaletteEntries - 1 do
  488. with Pal[I] do
  489. begin
  490. R := Palette[I].R;
  491. G := Palette[I].G;
  492. B := Palette[I].B;
  493. end;
  494. Write(Handle, Pal, FmtInfo.PaletteEntries * SizeOf(TColor24Rec));
  495. finally
  496. FreeMemNil(Pal);
  497. end;
  498. end;
  499. if FUseRLE then
  500. // Save rle compressed mode images
  501. SaveRLE
  502. else
  503. // Save uncompressed mode images
  504. Write(Handle, Bits, Size);
  505. Result := True;
  506. finally
  507. if MustBeFreed then
  508. FreeImage(ImageToSave);
  509. end;
  510. end;
  511. procedure TTargaFileFormat.ConvertToSupported(var Image: TImageData;
  512. const Info: TImageFormatInfo);
  513. var
  514. ConvFormat: TImageFormat;
  515. begin
  516. if Info.HasGrayChannel then
  517. // Convert all grayscale images to Gray8 (preserve alpha of AxGrayx formats)
  518. ConvFormat := IffFormat(not Info.HasAlphaChannel, ifGray8, ifA8R8G8B8)
  519. else if Info.IsIndexed then
  520. // Convert all indexed images to Index8
  521. ConvFormat := ifIndex8
  522. else if Info.HasAlphaChannel then
  523. // Convert images with alpha channel to A8R8G8B8
  524. ConvFormat := ifA8R8G8B8
  525. else if Info.UsePixelFormat then
  526. // Convert 16bit images (without alpha channel) to A1R5G5B5
  527. ConvFormat := ifA1R5G5B5
  528. else
  529. // Convert all other formats to R8G8B8
  530. ConvFormat := ifR8G8B8;
  531. ConvertImage(Image, ConvFormat);
  532. end;
  533. function TTargaFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  534. var
  535. Hdr: TTargaHeader;
  536. ReadCount: LongInt;
  537. begin
  538. Result := False;
  539. if Handle <> nil then
  540. begin
  541. ReadCount := GetIO.Read(Handle, @Hdr, SizeOf(Hdr));
  542. GetIO.Seek(Handle, -ReadCount, smFromCurrent);
  543. Result := (ReadCount >= SizeOf(Hdr)) and
  544. (Hdr.ImageType in [0, 1, 2, 3, 9, 10, 11]) and
  545. (Hdr.PixelSize in [1, 8, 15, 16, 24, 32]) and
  546. (Hdr.ColorEntrySize in [0, 16, 24, 32]);
  547. end;
  548. end;
  549. initialization
  550. RegisterImageFileFormat(TTargaFileFormat);
  551. {
  552. File Notes:
  553. -- TODOS ----------------------------------------------------
  554. - nothing now
  555. -- 0.21 Changes/Bug Fixes -----------------------------------
  556. - MakeCompatible method moved to base class, put ConvertToSupported here.
  557. GetSupportedFormats removed, it is now set in constructor.
  558. - Made public properties for options registered to SetOption/GetOption
  559. functions.
  560. - Changed extensions to filename masks.
  561. - Changed SaveData, LoadData, and MakeCompatible methods according
  562. to changes in base class in Imaging unit.
  563. -- 0.17 Changes/Bug Fixes -----------------------------------
  564. - 16 bit images are usually without alpha but some has alpha
  565. channel and there is no indication of it - so I have added
  566. a check: if all pixels of image are with alpha = 0 image is treated
  567. as X1R5G5B5 otherwise as A1R5G5B5
  568. - fixed problems with some nonstandard 15 bit images
  569. }
  570. end.