You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PixelFormat.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright 2009 Pierre Ossman for Cendio AB
  3. * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
  4. * Copyright (C) 2011 Brian P. Hinz
  5. *
  6. * This is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This software is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this software; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  19. * USA.
  20. */
  21. //
  22. // PixelFormat
  23. //
  24. package com.tigervnc.rfb;
  25. import java.awt.color.*;
  26. import java.awt.image.*;
  27. import java.nio.*;
  28. import java.util.*;
  29. import com.tigervnc.rdr.*;
  30. public class PixelFormat {
  31. public PixelFormat(int b, int d, boolean e, boolean t,
  32. int rm, int gm, int bm, int rs, int gs, int bs)
  33. {
  34. bpp = b; depth = d; trueColour = t; bigEndian = e;
  35. redMax = rm; greenMax = gm; blueMax = bm;
  36. redShift = rs; greenShift = gs; blueShift = bs;
  37. converters = new HashMap<Integer, ColorConvertOp>();
  38. if (!isSane())
  39. throw new Exception("invalid pixel format");
  40. updateState();
  41. }
  42. public PixelFormat()
  43. {
  44. this(8, 8, false, true, 7, 7, 3, 0, 3, 6);
  45. updateState();
  46. }
  47. public boolean equal(PixelFormat other)
  48. {
  49. if (bpp != other.bpp || depth != other.depth)
  50. return false;
  51. if (redMax != other.redMax)
  52. return false;
  53. if (greenMax != other.greenMax)
  54. return false;
  55. if (blueMax != other.blueMax)
  56. return false;
  57. // Endianness requires more care to determine compatibility
  58. if (bigEndian == other.bigEndian || bpp == 8) {
  59. if (redShift != other.redShift)
  60. return false;
  61. if (greenShift != other.greenShift)
  62. return false;
  63. if (blueShift != other.blueShift)
  64. return false;
  65. } else {
  66. // Has to be the same byte for each channel
  67. if (redShift/8 != (3 - other.redShift/8))
  68. return false;
  69. if (greenShift/8 != (3 - other.greenShift/8))
  70. return false;
  71. if (blueShift/8 != (3 - other.blueShift/8))
  72. return false;
  73. // And the same bit offset within the byte
  74. if (redShift%8 != other.redShift%8)
  75. return false;
  76. if (greenShift%8 != other.greenShift%8)
  77. return false;
  78. if (blueShift%8 != other.blueShift%8)
  79. return false;
  80. // And not cross a byte boundary
  81. if (redShift/8 != (redShift + redBits - 1)/8)
  82. return false;
  83. if (greenShift/8 != (greenShift + greenBits - 1)/8)
  84. return false;
  85. if (blueShift/8 != (blueShift + blueBits - 1)/8)
  86. return false;
  87. }
  88. return true;
  89. }
  90. public void read(InStream is) {
  91. bpp = is.readU8();
  92. depth = is.readU8();
  93. bigEndian = is.readU8()!=0;
  94. trueColour = is.readU8()!=0;
  95. redMax = is.readU16();
  96. greenMax = is.readU16();
  97. blueMax = is.readU16();
  98. redShift = is.readU8();
  99. greenShift = is.readU8();
  100. blueShift = is.readU8();
  101. is.skip(3);
  102. // We have no real support for colour maps. If the client
  103. // wants one, then we force a 8-bit true colour format and
  104. // pretend it's a colour map.
  105. if (!trueColour) {
  106. redMax = 7;
  107. greenMax = 7;
  108. blueMax = 3;
  109. redShift = 0;
  110. greenShift = 3;
  111. blueShift = 6;
  112. }
  113. if (!isSane())
  114. throw new Exception("invalid pixel format: "+print());
  115. updateState();
  116. }
  117. public void write(OutStream os) {
  118. os.writeU8(bpp);
  119. os.writeU8(depth);
  120. os.writeU8(bigEndian?1:0);
  121. os.writeU8(trueColour?1:0);
  122. os.writeU16(redMax);
  123. os.writeU16(greenMax);
  124. os.writeU16(blueMax);
  125. os.writeU8(redShift);
  126. os.writeU8(greenShift);
  127. os.writeU8(blueShift);
  128. os.pad(3);
  129. }
  130. public final boolean isBigEndian() {
  131. return bigEndian;
  132. }
  133. public final boolean isLittleEndian() {
  134. return ! bigEndian;
  135. }
  136. public final boolean is888() {
  137. if(!trueColour)
  138. return false;
  139. if(bpp != 32)
  140. return false;
  141. if(depth != 24)
  142. return false;
  143. if(redMax != 255)
  144. return false;
  145. if(greenMax != 255)
  146. return false;
  147. if(blueMax != 255)
  148. return false;
  149. if ((redShift & 0x7) != 0)
  150. return false;
  151. if ((greenShift & 0x7) != 0)
  152. return false;
  153. if ((blueShift & 0x7) != 0)
  154. return false;
  155. return true;
  156. }
  157. public int pixelFromRGB(int red, int green, int blue, ColorModel cm)
  158. {
  159. if (trueColour) {
  160. int r = (red * redMax + 32767) / 65535;
  161. int g = (green * greenMax + 32767) / 65535;
  162. int b = (blue * blueMax + 32767) / 65535;
  163. return (r << redShift) | (g << greenShift) | (b << blueShift);
  164. } else if (cm != null) {
  165. // Try to find the closest pixel by Cartesian distance
  166. int colours = 1 << depth;
  167. int diff = 256 * 256 * 4;
  168. int col = 0;
  169. for (int i=0; i<colours; i++) {
  170. int r, g, b;
  171. r = cm.getRed(i);
  172. g = cm.getGreen(i);
  173. b = cm.getBlue(i);
  174. int rd = (r-red) >> 8;
  175. int gd = (g-green) >> 8;
  176. int bd = (b-blue) >> 8;
  177. int d = rd*rd + gd*gd + bd*bd;
  178. if (d < diff) {
  179. col = i;
  180. diff = d;
  181. }
  182. }
  183. return col;
  184. }
  185. // XXX just return 0 for colour map?
  186. return 0;
  187. }
  188. // This method should be invoked with duplicates of dst/src Buffers
  189. public void bufferFromRGB(ByteBuffer dst, ByteBuffer src, int pixels)
  190. {
  191. bufferFromRGB(dst, src, pixels, pixels, 1);
  192. }
  193. // This method should be invoked with duplicates of dst/src Buffers
  194. public void bufferFromRGB(ByteBuffer dst, ByteBuffer src,
  195. int w, int stride, int h)
  196. {
  197. if (is888()) {
  198. // Optimised common case
  199. int r, g, b, x;
  200. if (bigEndian) {
  201. r = dst.position() + (24 - redShift)/8;
  202. g = dst.position() + (24 - greenShift)/8;
  203. b = dst.position() + (24 - blueShift)/8;
  204. x = dst.position() + (24 - (48 - redShift - greenShift - blueShift))/8;
  205. } else {
  206. r = dst.position() + redShift/8;
  207. g = dst.position() + greenShift/8;
  208. b = dst.position() + blueShift/8;
  209. x = dst.position() + (48 - redShift - greenShift - blueShift)/8;
  210. }
  211. int dstPad = (stride - w) * 4;
  212. while (h-- > 0) {
  213. int w_ = w;
  214. while (w_-- > 0) {
  215. dst.put(r, src.get());
  216. dst.put(g, src.get());
  217. dst.put(b, src.get());
  218. dst.put(x, (byte)0);
  219. r += 4;
  220. g += 4;
  221. b += 4;
  222. x += 4;
  223. }
  224. r += dstPad;
  225. g += dstPad;
  226. b += dstPad;
  227. x += dstPad;
  228. }
  229. } else {
  230. // Generic code
  231. int dstPad = (stride - w) * bpp/8;
  232. while (h-- > 0) {
  233. int w_ = w;
  234. while (w_-- > 0) {
  235. int p;
  236. int r, g, b;
  237. r = src.get();
  238. g = src.get();
  239. b = src.get();
  240. p = pixelFromRGB(r, g, b, model);
  241. bufferFromPixel(dst, p);
  242. dst.position(dst.position() + bpp/8);
  243. }
  244. dst.position(dst.position() + dstPad);
  245. }
  246. }
  247. }
  248. // This method should be invoked with duplicates of dst/src Buffers
  249. public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src, int pixels)
  250. {
  251. rgbFromBuffer(dst, src, pixels, pixels, 1);
  252. }
  253. // This method should be invoked with duplicates of dst/src Buffers
  254. public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src,
  255. int w, int stride, int h)
  256. {
  257. if (is888()) {
  258. // Optimised common case
  259. int r, g, b;
  260. if (bigEndian) {
  261. r = src.position() + (24 - redShift)/8;
  262. g = src.position() + (24 - greenShift)/8;
  263. b = src.position() + (24 - blueShift)/8;
  264. } else {
  265. r = src.position() + redShift/8;
  266. g = src.position() + greenShift/8;
  267. b = src.position() + blueShift/8;
  268. }
  269. int srcPad = (stride - w) * 4;
  270. while (h-- > 0) {
  271. int w_ = w;
  272. while (w_-- > 0) {
  273. dst.put(src.get(r));
  274. dst.put(src.get(g));
  275. dst.put(src.get(b));
  276. r += 4;
  277. g += 4;
  278. b += 4;
  279. }
  280. r += srcPad;
  281. g += srcPad;
  282. b += srcPad;
  283. }
  284. } else {
  285. // Generic code
  286. int srcPad = (stride - w) * bpp/8;
  287. while (h-- > 0) {
  288. int w_ = w;
  289. while (w_-- > 0) {
  290. int p;
  291. byte r, g, b;
  292. p = pixelFromBuffer(src.duplicate());
  293. r = (byte)getColorModel().getRed(p);
  294. g = (byte)getColorModel().getGreen(p);
  295. b = (byte)getColorModel().getBlue(p);
  296. dst.put(r);
  297. dst.put(g);
  298. dst.put(b);
  299. src.position(src.position() + bpp/8);
  300. }
  301. src.position(src.position() + srcPad);
  302. }
  303. }
  304. }
  305. public void rgbFromPixels(byte[] dst, int dstPtr, int[] src, int srcPtr, int pixels, ColorModel cm)
  306. {
  307. int p;
  308. byte r, g, b;
  309. for (int i=0; i < pixels; i++) {
  310. p = src[i];
  311. dst[dstPtr++] = (byte)cm.getRed(p);
  312. dst[dstPtr++] = (byte)cm.getGreen(p);
  313. dst[dstPtr++] = (byte)cm.getBlue(p);
  314. }
  315. }
  316. // This method should be invoked with a duplicates of buffer
  317. public int pixelFromBuffer(ByteBuffer buffer)
  318. {
  319. int p;
  320. p = 0xff000000;
  321. if (!bigEndian) {
  322. switch (bpp) {
  323. case 32:
  324. p |= buffer.get() << 24;
  325. p |= buffer.get() << 16;
  326. case 16:
  327. p |= buffer.get() << 8;
  328. case 8:
  329. p |= buffer.get();
  330. }
  331. } else {
  332. p |= buffer.get(0);
  333. if (bpp >= 16) {
  334. p |= buffer.get(1) << 8;
  335. if (bpp == 32) {
  336. p |= buffer.get(2) << 16;
  337. p |= buffer.get(3) << 24;
  338. }
  339. }
  340. }
  341. return p;
  342. }
  343. public String print() {
  344. StringBuffer s = new StringBuffer();
  345. s.append("depth "+depth+" ("+bpp+"bpp)");
  346. if (bpp != 8) {
  347. if (bigEndian)
  348. s.append(" big-endian");
  349. else
  350. s.append(" little-endian");
  351. }
  352. if (!trueColour) {
  353. s.append(" colour-map");
  354. return s.toString();
  355. }
  356. if (blueShift == 0 && greenShift > blueShift && redShift > greenShift &&
  357. blueMax == (1 << greenShift) - 1 &&
  358. greenMax == (1 << (redShift-greenShift)) - 1 &&
  359. redMax == (1 << (depth-redShift)) - 1)
  360. {
  361. s.append(" rgb"+(depth-redShift)+(redShift-greenShift)+greenShift);
  362. return s.toString();
  363. }
  364. if (redShift == 0 && greenShift > redShift && blueShift > greenShift &&
  365. redMax == (1 << greenShift) - 1 &&
  366. greenMax == (1 << (blueShift-greenShift)) - 1 &&
  367. blueMax == (1 << (depth-blueShift)) - 1)
  368. {
  369. s.append(" bgr"+(depth-blueShift)+(blueShift-greenShift)+greenShift);
  370. return s.toString();
  371. }
  372. s.append(" rgb max "+redMax+","+greenMax+","+blueMax+" shift "+redShift+
  373. ","+greenShift+","+blueShift);
  374. return s.toString();
  375. }
  376. private static int bits(int value)
  377. {
  378. int bits;
  379. bits = 16;
  380. if ((value & 0xff00) == 0) {
  381. bits -= 8;
  382. value <<= 8;
  383. }
  384. if ((value & 0xf000) == 0) {
  385. bits -= 4;
  386. value <<= 4;
  387. }
  388. if ((value & 0xc000) == 0) {
  389. bits -= 2;
  390. value <<= 2;
  391. }
  392. if ((value & 0x8000) == 0) {
  393. bits -= 1;
  394. value <<= 1;
  395. }
  396. return bits;
  397. }
  398. private void updateState()
  399. {
  400. int endianTest = 1;
  401. redBits = bits(redMax);
  402. greenBits = bits(greenMax);
  403. blueBits = bits(blueMax);
  404. maxBits = redBits;
  405. if (greenBits > maxBits)
  406. maxBits = greenBits;
  407. if (blueBits > maxBits)
  408. maxBits = blueBits;
  409. minBits = redBits;
  410. if (greenBits < minBits)
  411. minBits = greenBits;
  412. if (blueBits < minBits)
  413. minBits = blueBits;
  414. if ((((char)endianTest) == 0) != bigEndian)
  415. endianMismatch = true;
  416. else
  417. endianMismatch = false;
  418. model = getColorModel(this);
  419. }
  420. private boolean isSane()
  421. {
  422. int totalBits;
  423. if ((bpp != 8) && (bpp != 16) && (bpp != 32))
  424. return false;
  425. if (depth > bpp)
  426. return false;
  427. if (!trueColour && (depth != 8))
  428. return false;
  429. if ((redMax & (redMax + 1)) != 0)
  430. return false;
  431. if ((greenMax & (greenMax + 1)) != 0)
  432. return false;
  433. if ((blueMax & (blueMax + 1)) != 0)
  434. return false;
  435. /*
  436. * We don't allow individual channels > 8 bits in order to keep our
  437. * conversions simple.
  438. */
  439. if (redMax >= (1 << 8))
  440. return false;
  441. if (greenMax >= (1 << 8))
  442. return false;
  443. if (blueMax >= (1 << 8))
  444. return false;
  445. totalBits = bits(redMax) + bits(greenMax) + bits(blueMax);
  446. if (totalBits > depth)
  447. return false;
  448. if ((bits(redMax) + redShift) > bpp)
  449. return false;
  450. if ((bits(greenMax) + greenShift) > bpp)
  451. return false;
  452. if ((bits(blueMax) + blueShift) > bpp)
  453. return false;
  454. if (((redMax << redShift) & (greenMax << greenShift)) != 0)
  455. return false;
  456. if (((redMax << redShift) & (blueMax << blueShift)) != 0)
  457. return false;
  458. if (((greenMax << greenShift) & (blueMax << blueShift)) != 0)
  459. return false;
  460. return true;
  461. }
  462. public void bufferFromPixel(ByteBuffer buffer, int p)
  463. {
  464. if (bigEndian) {
  465. switch (bpp) {
  466. case 32:
  467. buffer.put((byte)((p >> 24) & 0xff));
  468. buffer.put((byte)((p >> 16) & 0xff));
  469. break;
  470. case 16:
  471. buffer.put((byte)((p >> 8) & 0xff));
  472. break;
  473. case 8:
  474. buffer.put((byte)((p >> 0) & 0xff));
  475. break;
  476. }
  477. } else {
  478. buffer.put(0, (byte)((p >> 0) & 0xff));
  479. if (bpp >= 16) {
  480. buffer.put(1, (byte)((p >> 8) & 0xff));
  481. if (bpp == 32) {
  482. buffer.put(2, (byte)((p >> 16) & 0xff));
  483. buffer.put(3, (byte)((p >> 24) & 0xff));
  484. }
  485. }
  486. }
  487. }
  488. public ColorModel getColorModel()
  489. {
  490. return model;
  491. }
  492. public static ColorModel getColorModel(PixelFormat pf) {
  493. if (!(pf.bpp == 32) && !(pf.bpp == 16) && !(pf.bpp == 8))
  494. throw new Exception("Internal error: bpp must be 8, 16, or 32 in PixelBuffer ("+pf.bpp+")");
  495. ColorModel cm;
  496. switch (pf.depth) {
  497. case 3:
  498. // Fall-through to depth 8
  499. case 6:
  500. // Fall-through to depth 8
  501. case 8:
  502. int rmask = pf.redMax << pf.redShift;
  503. int gmask = pf.greenMax << pf.greenShift;
  504. int bmask = pf.blueMax << pf.blueShift;
  505. cm = new DirectColorModel(8, rmask, gmask, bmask);
  506. break;
  507. case 16:
  508. cm = new DirectColorModel(32, 0xF800, 0x07C0, 0x003E);
  509. break;
  510. case 24:
  511. cm = new DirectColorModel(32, (0xff << 16), (0xff << 8), 0xff);
  512. break;
  513. case 32:
  514. cm = new DirectColorModel(32, (0xff << pf.redShift),
  515. (0xff << pf.greenShift), (0xff << pf.blueShift));
  516. break;
  517. default:
  518. throw new Exception("Unsupported color depth ("+pf.depth+")");
  519. }
  520. assert(cm != null);
  521. return cm;
  522. }
  523. public ColorConvertOp getColorConvertOp(ColorSpace src)
  524. {
  525. // The overhead associated with initializing ColorConvertOps is
  526. // enough to justify maintaining a static lookup table.
  527. if (converters.containsKey(src.getType()))
  528. return converters.get(src.getType());
  529. ColorSpace dst = model.getColorSpace();
  530. converters.put(src.getType(), new ColorConvertOp(src, dst, null));
  531. return converters.get(src.getType());
  532. }
  533. public ByteOrder getByteOrder()
  534. {
  535. if (isBigEndian())
  536. return ByteOrder.BIG_ENDIAN;
  537. else
  538. return ByteOrder.LITTLE_ENDIAN;
  539. }
  540. public Raster rasterFromBuffer(Rect r, ByteBuffer buf)
  541. {
  542. Buffer dst;
  543. DataBuffer db = null;
  544. SampleModel sm =
  545. model.createCompatibleSampleModel(r.width(), r.height());
  546. switch (sm.getTransferType()) {
  547. case DataBuffer.TYPE_INT:
  548. dst = IntBuffer.allocate(r.area()).put(buf.asIntBuffer());
  549. db = new DataBufferInt(((IntBuffer)dst).array(), r.area());
  550. break;
  551. case DataBuffer.TYPE_BYTE:
  552. db = new DataBufferByte(buf.array(), r.area());
  553. break;
  554. case DataBuffer.TYPE_SHORT:
  555. dst = ShortBuffer.allocate(r.area()).put(buf.asShortBuffer());
  556. db = new DataBufferShort(((ShortBuffer)dst).array(), r.area());
  557. break;
  558. }
  559. assert(db != null);
  560. return Raster.createRaster(sm, db, new java.awt.Point(0, 0));
  561. }
  562. private static HashMap<Integer, ColorConvertOp> converters;
  563. public int bpp;
  564. public int depth;
  565. public boolean bigEndian;
  566. public boolean trueColour;
  567. public int redMax;
  568. public int greenMax;
  569. public int blueMax;
  570. public int redShift;
  571. public int greenShift;
  572. public int blueShift;
  573. protected int redBits, greenBits, blueBits;
  574. protected int maxBits, minBits;
  575. protected boolean endianMismatch;
  576. private ColorModel model;
  577. }