PageRenderTime 32ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/src/application/extensions/GIFLoader.java

https://gitlab.com/Smidqe/gmanager
Java | 472 lines | 355 code | 61 blank | 56 comment | 96 complexity | 507510efbff688b5621aa3d767721a6e MD5 | raw file
  1. /*
  2. * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
  3. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  4. *
  5. *
  6. *
  7. *
  8. *
  9. *
  10. *
  11. *
  12. *
  13. *
  14. *
  15. *
  16. *
  17. *
  18. *
  19. *
  20. *
  21. *
  22. *
  23. *
  24. */
  25. package application.extensions;
  26. import com.sun.javafx.iio.ImageFrame;
  27. import com.sun.javafx.iio.ImageMetadata;
  28. import com.sun.javafx.iio.ImageStorage;
  29. import com.sun.javafx.iio.common.ImageLoaderImpl;
  30. import com.sun.javafx.iio.common.ImageTools;
  31. import com.sun.javafx.iio.gif.GIFDescriptor;
  32. import java.io.EOFException;
  33. import java.io.IOException;
  34. import java.io.InputStream;
  35. import java.nio.ByteBuffer;
  36. import java.util.Arrays;
  37. /*
  38. * loader implementation for GIF89 file format
  39. */
  40. public class GIFLoader extends ImageLoaderImpl {
  41. static final byte FILE_SIG87[] = {'G', 'I', 'F', '8', '7', 'a'};
  42. static final byte FILE_SIG89[] = {'G', 'I', 'F', '8', '9', 'a'};
  43. static final byte NETSCAPE_SIG[] = {'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0'};
  44. static final int DEFAULT_FPS = 25;
  45. InputStream stream = null;
  46. int screenW, screenH, bgColor;
  47. byte globalPalette[][]; // r,g,b,a
  48. byte image[];
  49. int loopCount = 1;
  50. public GIFLoader(InputStream input) throws IOException {
  51. super(GIFDescriptor.getInstance());
  52. this.stream = input;
  53. readGlobalHeader();
  54. }
  55. // read GIF file header
  56. private void readGlobalHeader() throws IOException {
  57. byte signature[] = readBytes(new byte[6]);
  58. if (!Arrays.equals(FILE_SIG87, signature) && !Arrays.equals(FILE_SIG89, signature)) {
  59. throw new IOException("Bad GIF signature!");
  60. }
  61. screenW = readShort();
  62. screenH = readShort();
  63. int cInfo = readByte();
  64. bgColor = readByte();
  65. int aspectR = readByte();
  66. if ((cInfo & 0x80) != 0) {
  67. globalPalette = readPalete(2 << (cInfo & 7), -1);
  68. }
  69. image = new byte[screenW * screenH * 4];
  70. }
  71. // read palette data from the stream
  72. private byte[][] readPalete(int size, int trnsIndex) throws IOException {
  73. byte palette[][] = new byte[4][size];
  74. byte paletteData[] = readBytes(new byte[size*3]);
  75. for (int i = 0, idx = 0; i != size; ++i) {
  76. for (int k = 0; k != 3; ++k) {
  77. palette[k][i] = paletteData[idx++];
  78. }
  79. palette[3][i] = (i == trnsIndex) ? 0 : (byte)0xFF;
  80. }
  81. return palette;
  82. }
  83. // skip an extension
  84. private void consumeAnExtension() throws IOException {
  85. for (int blSize = readByte(); blSize != 0; blSize = readByte()) {
  86. skipBytes(blSize);
  87. }
  88. }
  89. private void readAppExtension() throws IOException {
  90. int size = readByte();
  91. byte buf[] = readBytes(new byte[size]);
  92. if (Arrays.equals(NETSCAPE_SIG, buf)) {
  93. for (int subBlockSize = readByte(); subBlockSize != 0; subBlockSize = readByte()) {
  94. byte subBlock[] = readBytes(new byte[subBlockSize]);
  95. int subBlockId = subBlock[0];
  96. if (subBlockSize == 3 && subBlockId == 1) { // loop count extension
  97. loopCount = (subBlock[1] & 0xff) | ((subBlock[2] & 0xff) << 8);
  98. }
  99. }
  100. } else {
  101. consumeAnExtension(); // read data sub-blocks
  102. }
  103. }
  104. // reads Image Control extension information
  105. // returns ((pField & 0x1F) << 24) + (trnsIndex << 16) + frameDelay;
  106. private int readControlCode() throws IOException {
  107. int size = readByte();
  108. int pField = readByte();
  109. int frameDelay = readShort();
  110. int trnsIndex = readByte();
  111. if (size != 4 || readByte() != 0) {
  112. throw new IOException("Bad GIF GraphicControlExtension");
  113. }
  114. return ((pField & 0x1F) << 24) + (trnsIndex << 16) + frameDelay;
  115. }
  116. // The method waits until image data in the stream
  117. // The method also reads and return Image Control extension information
  118. // returns -1 if EOF reached or the value of readControlCode
  119. private int waitForImageFrame() throws IOException {
  120. int controlData = 0;
  121. while (true) {
  122. int ch = stream.read();
  123. switch (ch) {
  124. case 0x2C:
  125. return controlData;
  126. case 0x21:
  127. switch (readByte()) {
  128. case 0xF9:
  129. controlData = readControlCode();
  130. break;
  131. case 0xFF:
  132. readAppExtension();
  133. break;
  134. default:
  135. consumeAnExtension();
  136. }
  137. break;
  138. case -1: case 0x3B: // EOF or end of GIF
  139. return -1;
  140. default:
  141. throw new IOException("Unexpected GIF control characher 0x"
  142. + String.format("%02X", ch));
  143. }
  144. }
  145. }
  146. // Decode the one frame of GIF form the input stread using internal LZWDecoder class
  147. private void decodeImage(byte image[], int w, int h, int interlace[]) throws IOException {
  148. LZWDecoder dec = new LZWDecoder();
  149. byte data[] = dec.getString();
  150. int y = 0, iPos = 0, xr = w;
  151. while (true) {
  152. int len = dec.readString();
  153. if (len == -1) { // end of stream
  154. dec.waitForTerminator();
  155. return;
  156. }
  157. for (int pos = 0; pos != len;) {
  158. int ax = xr < (len - pos) ? xr : (len - pos);
  159. System.arraycopy(data, pos, image, iPos, ax);
  160. iPos += ax;
  161. pos += ax;
  162. if ((xr -= ax) == 0) {
  163. if (++y == h) { // image is full
  164. dec.waitForTerminator();
  165. return;
  166. }
  167. int iY = interlace == null ? y : interlace[y];
  168. iPos = iY * w;
  169. xr = w;
  170. }
  171. }
  172. }
  173. }
  174. // computes row re-index for interlaced case
  175. private int[] computeInterlaceReIndex(int h) {
  176. int data[] = new int[h], pos = 0;
  177. for (int i = 0; i < h; i += 8) data[pos++] = i;
  178. for (int i = 4; i < h; i += 8) data[pos++] = i;
  179. for (int i = 2; i < h; i += 4) data[pos++] = i;
  180. for (int i = 1; i < h; i += 2) data[pos++] = i;
  181. return data;
  182. }
  183. // loads next image frame or null if no more
  184. public ImageFrame load(int imageIndex, int width, int height, boolean preserveAspectRatio, boolean smooth) throws IOException {
  185. int imageControlCode = waitForImageFrame();
  186. if (imageControlCode < 0) {
  187. return null;
  188. }
  189. int left = readShort(), top = readShort(), w = readShort(), h = readShort();
  190. // check if the image is in the virtual screen boundaries
  191. if (left + w > screenW || top + h > screenH) {
  192. throw new IOException("Wrong GIF image frame size");
  193. }
  194. int imgCtrl = readByte();
  195. boolean isTRNS = ((imageControlCode >>> 24) & 1) == 1;
  196. int trnsIndex = isTRNS ? (imageControlCode >>> 16) & 0xFF : -1;
  197. boolean localPalette = (imgCtrl & 0x80) != 0;
  198. boolean isInterlaced = (imgCtrl & 0x40) != 0;
  199. byte palette[][] = localPalette ? readPalete(2 << (imgCtrl & 7), trnsIndex) : globalPalette;
  200. int[] outWH = ImageTools.computeDimensions(screenW, screenH, width, height, preserveAspectRatio);
  201. width = outWH[0];
  202. height = outWH[1];
  203. ImageMetadata metadata = updateMetadata(width, height, imageControlCode & 0xFFFF);
  204. int disposalCode = (imageControlCode >>> 26) & 7;
  205. byte pImage[] = new byte[w * h];
  206. decodeImage(pImage, w, h, isInterlaced ? computeInterlaceReIndex(h) : null);
  207. ByteBuffer img = decodePalette(pImage, palette, trnsIndex,
  208. left, top, w, h, disposalCode);
  209. if (screenW != width || screenH != height) {
  210. img = ImageTools.scaleImage(img, screenW, screenH, 4,
  211. width, height, smooth);
  212. }
  213. return new ImageFrame(ImageStorage.ImageType.RGBA, img,
  214. width, height, width * 4, null, metadata);
  215. }
  216. // IO helpers
  217. private int readByte() throws IOException {
  218. int ch = stream.read();
  219. if (ch < 0) {
  220. throw new EOFException();
  221. }
  222. return ch;
  223. }
  224. private int readShort() throws IOException {
  225. int lsb = readByte(), msb = readByte();
  226. return lsb + (msb << 8);
  227. }
  228. private byte[] readBytes(byte data[]) throws IOException {
  229. return readBytes(data, 0, data.length);
  230. }
  231. private byte[] readBytes(byte data[], int offs, int size) throws IOException {
  232. while (size > 0) {
  233. int sz = stream.read(data, offs, size);
  234. if (sz < 0) {
  235. throw new EOFException();
  236. }
  237. offs += sz;
  238. size -= sz;
  239. }
  240. return data;
  241. }
  242. private void skipBytes(int n) throws IOException {
  243. ImageTools.skipFully(stream, n);
  244. }
  245. public void dispose() {}
  246. // GIF specification states that restore to background should fill the frame
  247. // with background color, but actually all modern programs fill with transparent color.
  248. private void restoreToBackground(byte img[], int left, int top, int w, int h) {
  249. for (int y = 0; y != h; ++y) {
  250. int iPos = ((top + y) * screenW + left) * 4;
  251. for (int x = 0; x != w; iPos += 4, ++x) {
  252. img[iPos + 3] = 0;
  253. }
  254. }
  255. }
  256. // decode palletized image into RGBA
  257. private ByteBuffer decodePalette(byte[] srcImage, byte[][] palette, int trnsIndex,
  258. int left, int top, int w, int h, int disposalCode) {
  259. byte img[] = (disposalCode == 3) ? image.clone() : image;
  260. for (int y = 0; y != h; ++y) {
  261. int iPos = ((top + y) * screenW + left) * 4;
  262. int i = y * w;
  263. if (trnsIndex < 0) {
  264. for (int x = 0; x != w; iPos += 4, ++x) {
  265. int index = 0xFF & srcImage[i + x];
  266. img[iPos + 0] = palette[0][index];
  267. img[iPos + 1] = palette[1][index];
  268. img[iPos + 2] = palette[2][index];
  269. img[iPos + 3] = palette[3][index];
  270. }
  271. } else {
  272. for (int x = 0; x != w; iPos += 4, ++x) {
  273. int index = 0xFF & srcImage[i + x];
  274. if (index != trnsIndex) {
  275. img[iPos + 0] = palette[0][index];
  276. img[iPos + 1] = palette[1][index];
  277. img[iPos + 2] = palette[2][index];
  278. img[iPos + 3] = palette[3][index];
  279. }
  280. }
  281. }
  282. }
  283. if (disposalCode != 3) img = img.clone();
  284. if (disposalCode == 2) restoreToBackground(image, left, top, w, h);
  285. return ByteBuffer.wrap(img);
  286. }
  287. // fill metadata
  288. private ImageMetadata updateMetadata(int w, int h, int delayTime) {
  289. ImageMetadata metaData = new ImageMetadata(null, true, null, null, null,
  290. delayTime != 0 ? delayTime*10 : 1000/DEFAULT_FPS, loopCount, w, h, null, null, null);
  291. updateImageMetadata(metaData);
  292. return metaData;
  293. }
  294. class LZWDecoder {
  295. private final int initCodeSize, clearCode, eofCode;
  296. private int codeSize, codeMask, tableIndex, oldCode;
  297. // input data buffer
  298. private int blockLength = 0, blockPos = 0;
  299. private byte block[] = new byte[255];
  300. private int inData = 0, inBits = 0;
  301. // table
  302. private int[] prefix = new int[4096];
  303. private byte[] suffix = new byte[4096];
  304. private byte[] initial = new byte[4096];
  305. private int[] length = new int[4096];
  306. private byte[] string = new byte[4096];
  307. public LZWDecoder() throws IOException {
  308. initCodeSize = readByte();
  309. clearCode = 1 << initCodeSize;
  310. eofCode = clearCode + 1;
  311. initTable();
  312. }
  313. // decode next string of data, which can be accessed by getString() method
  314. public final int readString() throws IOException {
  315. int code = getCode();
  316. if (code == eofCode) {
  317. return -1;
  318. } else if (code == clearCode) {
  319. initTable();
  320. code = getCode();
  321. if (code == eofCode) {
  322. return -1;
  323. }
  324. } else {
  325. int newSuffixIndex;
  326. int ti = tableIndex;
  327. if (code < ti) {
  328. newSuffixIndex = code;
  329. } else { // code == tableIndex
  330. newSuffixIndex = oldCode;
  331. if (code != ti) {
  332. throw new IOException("Bad GIF LZW: Out-of-sequence code!");
  333. }
  334. }
  335. int oc = oldCode;
  336. prefix[ti] = oc;
  337. suffix[ti] = initial[newSuffixIndex];
  338. initial[ti] = initial[oc];
  339. length[ti] = length[oc] + 1;
  340. ++tableIndex;
  341. if ((tableIndex == (1 << codeSize)) && (tableIndex < 4096)) {
  342. ++codeSize;
  343. codeMask = (1 << codeSize) - 1;
  344. }
  345. }
  346. // Reverse code
  347. int c = code;
  348. int len = length[c];
  349. for (int i = len - 1; i >= 0; i--) {
  350. string[i] = suffix[c];
  351. c = prefix[c];
  352. }
  353. oldCode = code;
  354. return len;
  355. }
  356. // data accessor, the data length returned by readString method
  357. public final byte[] getString() { return string; }
  358. // waits until data ends
  359. public final void waitForTerminator() throws IOException {
  360. consumeAnExtension();
  361. }
  362. // initialize LZW dctionary
  363. private void initTable() {
  364. int numEntries = 1 << initCodeSize;
  365. for (int i = 0; i < numEntries; i++) {
  366. prefix[i] = -1;
  367. suffix[i] = (byte) i;
  368. initial[i] = (byte) i;
  369. length[i] = 1;
  370. }
  371. // fill in the entire table for robustness against
  372. // out-of-sequence codes.
  373. for (int i = numEntries; i < 4096; i++) {
  374. prefix[i] = -1;
  375. length[i] = 1;
  376. }
  377. codeSize = initCodeSize + 1;
  378. codeMask = (1 << codeSize) - 1;
  379. tableIndex = numEntries + 2;
  380. oldCode = 0;
  381. }
  382. // reads codeSize bits from the stream
  383. private int getCode() throws IOException {
  384. while (inBits < codeSize) {
  385. inData |= nextByte() << inBits;
  386. inBits += 8;
  387. }
  388. int code = inData & codeMask;
  389. inBits -= codeSize;
  390. inData >>>= codeSize;
  391. return code;
  392. }
  393. // reads next in byte
  394. private int nextByte() throws IOException {
  395. if (blockPos == blockLength) {
  396. readData();
  397. }
  398. return (int)block[blockPos++] & 0xFF;
  399. }
  400. // reads next block if data
  401. private void readData() throws IOException {
  402. blockPos = 0;
  403. blockLength = readByte();
  404. if (blockLength > 0) {
  405. readBytes(block, 0, blockLength);
  406. } else {
  407. throw new EOFException();
  408. }
  409. }
  410. }
  411. }