/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright 2009 Pierre Ossman for Cendio AB * Copyright (C) 2011 D. R. Commander. All Rights Reserved. * Copyright (C) 2011 Brian P. Hinz * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ // // PixelFormat // package com.tigervnc.rfb; import java.awt.color.*; import java.awt.image.*; import java.nio.*; import java.util.*; import com.tigervnc.rdr.*; public class PixelFormat { public PixelFormat(int b, int d, boolean e, boolean t, int rm, int gm, int bm, int rs, int gs, int bs) { bpp = b; depth = d; trueColour = t; bigEndian = e; redMax = rm; greenMax = gm; blueMax = bm; redShift = rs; greenShift = gs; blueShift = bs; converters = new HashMap(); if (!isSane()) throw new Exception("invalid pixel format"); updateState(); } public PixelFormat() { this(8, 8, false, true, 7, 7, 3, 0, 3, 6); updateState(); } public boolean equal(PixelFormat other) { if (bpp != other.bpp || depth != other.depth) return false; if (redMax != other.redMax) return false; if (greenMax != other.greenMax) return false; if (blueMax != other.blueMax) return false; // Endianness requires more care to determine compatibility if (bigEndian == other.bigEndian || bpp == 8) { if (redShift != other.redShift) return false; if (greenShift != other.greenShift) return false; if (blueShift != other.blueShift) return false; } else { // Has to be the same byte for each channel if (redShift/8 != (3 - other.redShift/8)) return false; if (greenShift/8 != (3 - other.greenShift/8)) return false; if (blueShift/8 != (3 - other.blueShift/8)) return false; // And the same bit offset within the byte if (redShift%8 != other.redShift%8) return false; if (greenShift%8 != other.greenShift%8) return false; if (blueShift%8 != other.blueShift%8) return false; // And not cross a byte boundary if (redShift/8 != (redShift + redBits - 1)/8) return false; if (greenShift/8 != (greenShift + greenBits - 1)/8) return false; if (blueShift/8 != (blueShift + blueBits - 1)/8) return false; } return true; } public void read(InStream is) { bpp = is.readU8(); depth = is.readU8(); bigEndian = is.readU8()!=0; trueColour = is.readU8()!=0; redMax = is.readU16(); greenMax = is.readU16(); blueMax = is.readU16(); redShift = is.readU8(); greenShift = is.readU8(); blueShift = is.readU8(); is.skip(3); // We have no real support for colour maps. If the client // wants one, then we force a 8-bit true colour format and // pretend it's a colour map. if (!trueColour) { redMax = 7; greenMax = 7; blueMax = 3; redShift = 0; greenShift = 3; blueShift = 6; } if (!isSane()) throw new Exception("invalid pixel format: "+print()); updateState(); } public void write(OutStream os) { os.writeU8(bpp); os.writeU8(depth); os.writeU8(bigEndian?1:0); os.writeU8(trueColour?1:0); os.writeU16(redMax); os.writeU16(greenMax); os.writeU16(blueMax); os.writeU8(redShift); os.writeU8(greenShift); os.writeU8(blueShift); os.pad(3); } public final boolean isBigEndian() { return bigEndian; } public final boolean isLittleEndian() { return ! bigEndian; } public final boolean is888() { if(!trueColour) return false; if(bpp != 32) return false; if(depth != 24) return false; if(redMax != 255) return false; if(greenMax != 255) return false; if(blueMax != 255) return false; if ((redShift & 0x7) != 0) return false; if ((greenShift & 0x7) != 0) return false; if ((blueShift & 0x7) != 0) return false; return true; } public int pixelFromRGB(int red, int green, int blue, ColorModel cm) { if (trueColour) { int r = (red * redMax + 32767) / 65535; int g = (green * greenMax + 32767) / 65535; int b = (blue * blueMax + 32767) / 65535; return (r << redShift) | (g << greenShift) | (b << blueShift); } else if (cm != null) { // Try to find the closest pixel by Cartesian distance int colours = 1 << depth; int diff = 256 * 256 * 4; int col = 0; for (int i=0; i> 8; int gd = (g-green) >> 8; int bd = (b-blue) >> 8; int d = rd*rd + gd*gd + bd*bd; if (d < diff) { col = i; diff = d; } } return col; } // XXX just return 0 for colour map? return 0; } // This method should be invoked with duplicates of dst/src Buffers public void bufferFromRGB(ByteBuffer dst, ByteBuffer src, int pixels) { bufferFromRGB(dst, src, pixels, pixels, 1); } // This method should be invoked with duplicates of dst/src Buffers public void bufferFromRGB(ByteBuffer dst, ByteBuffer src, int w, int stride, int h) { if (is888()) { // Optimised common case int r, g, b, x; if (bigEndian) { r = dst.position() + (24 - redShift)/8; g = dst.position() + (24 - greenShift)/8; b = dst.position() + (24 - blueShift)/8; x = dst.position() + (24 - (48 - redShift - greenShift - blueShift))/8; } else { r = dst.position() + redShift/8; g = dst.position() + greenShift/8; b = dst.position() + blueShift/8; x = dst.position() + (48 - redShift - greenShift - blueShift)/8; } int dstPad = (stride - w) * 4; while (h-- > 0) { int w_ = w; while (w_-- > 0) { dst.put(r, src.get()); dst.put(g, src.get()); dst.put(b, src.get()); dst.put(x, (byte)0); r += 4; g += 4; b += 4; x += 4; } r += dstPad; g += dstPad; b += dstPad; x += dstPad; } } else { // Generic code int dstPad = (stride - w) * bpp/8; while (h-- > 0) { int w_ = w; while (w_-- > 0) { int p; int r, g, b; r = src.get(); g = src.get(); b = src.get(); p = pixelFromRGB(r, g, b, model); bufferFromPixel(dst, p); dst.position(dst.position() + bpp/8); } dst.position(dst.position() + dstPad); } } } // This method should be invoked with duplicates of dst/src Buffers public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src, int pixels) { rgbFromBuffer(dst, src, pixels, pixels, 1); } // This method should be invoked with duplicates of dst/src Buffers public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src, int w, int stride, int h) { if (is888()) { // Optimised common case int r, g, b; if (bigEndian) { r = src.position() + (24 - redShift)/8; g = src.position() + (24 - greenShift)/8; b = src.position() + (24 - blueShift)/8; } else { r = src.position() + redShift/8; g = src.position() + greenShift/8; b = src.position() + blueShift/8; } int srcPad = (stride - w) * 4; while (h-- > 0) { int w_ = w; while (w_-- > 0) { dst.put(src.get(r)); dst.put(src.get(g)); dst.put(src.get(b)); r += 4; g += 4; b += 4; } r += srcPad; g += srcPad; b += srcPad; } } else { // Generic code int srcPad = (stride - w) * bpp/8; while (h-- > 0) { int w_ = w; while (w_-- > 0) { int p; byte r, g, b; p = pixelFromBuffer(src.duplicate()); r = (byte)getColorModel().getRed(p); g = (byte)getColorModel().getGreen(p); b = (byte)getColorModel().getBlue(p); dst.put(r); dst.put(g); dst.put(b); src.position(src.position() + bpp/8); } src.position(src.position() + srcPad); } } } public void rgbFromPixels(byte[] dst, int dstPtr, int[] src, int srcPtr, int pixels, ColorModel cm) { int p; byte r, g, b; for (int i=0; i < pixels; i++) { p = src[i]; dst[dstPtr++] = (byte)cm.getRed(p); dst[dstPtr++] = (byte)cm.getGreen(p); dst[dstPtr++] = (byte)cm.getBlue(p); } } // This method should be invoked with a duplicates of buffer public int pixelFromBuffer(ByteBuffer buffer) { int p; p = 0xff000000; if (!bigEndian) { switch (bpp) { case 32: p |= buffer.get() << 24; p |= buffer.get() << 16; case 16: p |= buffer.get() << 8; case 8: p |= buffer.get(); } } else { p |= buffer.get(0); if (bpp >= 16) { p |= buffer.get(1) << 8; if (bpp == 32) { p |= buffer.get(2) << 16; p |= buffer.get(3) << 24; } } } return p; } public String print() { StringBuffer s = new StringBuffer(); s.append("depth "+depth+" ("+bpp+"bpp)"); if (bpp != 8) { if (bigEndian) s.append(" big-endian"); else s.append(" little-endian"); } if (!trueColour) { s.append(" colour-map"); return s.toString(); } if (blueShift == 0 && greenShift > blueShift && redShift > greenShift && blueMax == (1 << greenShift) - 1 && greenMax == (1 << (redShift-greenShift)) - 1 && redMax == (1 << (depth-redShift)) - 1) { s.append(" rgb"+(depth-redShift)+(redShift-greenShift)+greenShift); return s.toString(); } if (redShift == 0 && greenShift > redShift && blueShift > greenShift && redMax == (1 << greenShift) - 1 && greenMax == (1 << (blueShift-greenShift)) - 1 && blueMax == (1 << (depth-blueShift)) - 1) { s.append(" bgr"+(depth-blueShift)+(blueShift-greenShift)+greenShift); return s.toString(); } s.append(" rgb max "+redMax+","+greenMax+","+blueMax+" shift "+redShift+ ","+greenShift+","+blueShift); return s.toString(); } private static int bits(int value) { int bits; bits = 16; if ((value & 0xff00) == 0) { bits -= 8; value <<= 8; } if ((value & 0xf000) == 0) { bits -= 4; value <<= 4; } if ((value & 0xc000) == 0) { bits -= 2; value <<= 2; } if ((value & 0x8000) == 0) { bits -= 1; value <<= 1; } return bits; } private void updateState() { int endianTest = 1; redBits = bits(redMax); greenBits = bits(greenMax); blueBits = bits(blueMax); maxBits = redBits; if (greenBits > maxBits) maxBits = greenBits; if (blueBits > maxBits) maxBits = blueBits; minBits = redBits; if (greenBits < minBits) minBits = greenBits; if (blueBits < minBits) minBits = blueBits; if ((((char)endianTest) == 0) != bigEndian) endianMismatch = true; else endianMismatch = false; model = getColorModel(this); } private boolean isSane() { int totalBits; if ((bpp != 8) && (bpp != 16) && (bpp != 32)) return false; if (depth > bpp) return false; if (!trueColour && (depth != 8)) return false; if ((redMax & (redMax + 1)) != 0) return false; if ((greenMax & (greenMax + 1)) != 0) return false; if ((blueMax & (blueMax + 1)) != 0) return false; /* * We don't allow individual channels > 8 bits in order to keep our * conversions simple. */ if (redMax >= (1 << 8)) return false; if (greenMax >= (1 << 8)) return false; if (blueMax >= (1 << 8)) return false; totalBits = bits(redMax) + bits(greenMax) + bits(blueMax); if (totalBits > depth) return false; if ((bits(redMax) + redShift) > bpp) return false; if ((bits(greenMax) + greenShift) > bpp) return false; if ((bits(blueMax) + blueShift) > bpp) return false; if (((redMax << redShift) & (greenMax << greenShift)) != 0) return false; if (((redMax << redShift) & (blueMax << blueShift)) != 0) return false; if (((greenMax << greenShift) & (blueMax << blueShift)) != 0) return false; return true; } public void bufferFromPixel(ByteBuffer buffer, int p) { if (bigEndian) { switch (bpp) { case 32: buffer.put((byte)((p >> 24) & 0xff)); buffer.put((byte)((p >> 16) & 0xff)); break; case 16: buffer.put((byte)((p >> 8) & 0xff)); break; case 8: buffer.put((byte)((p >> 0) & 0xff)); break; } } else { buffer.put(0, (byte)((p >> 0) & 0xff)); if (bpp >= 16) { buffer.put(1, (byte)((p >> 8) & 0xff)); if (bpp == 32) { buffer.put(2, (byte)((p >> 16) & 0xff)); buffer.put(3, (byte)((p >> 24) & 0xff)); } } } } public ColorModel getColorModel() { return model; } public static ColorModel getColorModel(PixelFormat pf) { if (!(pf.bpp == 32) && !(pf.bpp == 16) && !(pf.bpp == 8)) throw new Exception("Internal error: bpp must be 8, 16, or 32 in PixelBuffer ("+pf.bpp+")"); ColorModel cm; switch (pf.depth) { case 3: // Fall-through to depth 8 case 6: // Fall-through to depth 8 case 8: int rmask = pf.redMax << pf.redShift; int gmask = pf.greenMax << pf.greenShift; int bmask = pf.blueMax << pf.blueShift; cm = new DirectColorModel(8, rmask, gmask, bmask); break; case 16: cm = new DirectColorModel(32, 0xF800, 0x07C0, 0x003E); break; case 24: cm = new DirectColorModel(32, (0xff << 16), (0xff << 8), 0xff); break; case 32: cm = new DirectColorModel(32, (0xff << pf.redShift), (0xff << pf.greenShift), (0xff << pf.blueShift)); break; default: throw new Exception("Unsupported color depth ("+pf.depth+")"); } assert(cm != null); return cm; } public ColorConvertOp getColorConvertOp(ColorSpace src) { // The overhead associated with initializing ColorConvertOps is // enough to justify maintaining a static lookup table. if (converters.containsKey(src.getType())) return converters.get(src.getType()); ColorSpace dst = model.getColorSpace(); converters.put(src.getType(), new ColorConvertOp(src, dst, null)); return converters.get(src.getType()); } public ByteOrder getByteOrder() { if (isBigEndian()) return ByteOrder.BIG_ENDIAN; else return ByteOrder.LITTLE_ENDIAN; } public Raster rasterFromBuffer(Rect r, ByteBuffer buf) { Buffer dst; DataBuffer db = null; SampleModel sm = model.createCompatibleSampleModel(r.width(), r.height()); switch (sm.getTransferType()) { case DataBuffer.TYPE_INT: dst = IntBuffer.allocate(r.area()).put(buf.asIntBuffer()); db = new DataBufferInt(((IntBuffer)dst).array(), r.area()); break; case DataBuffer.TYPE_BYTE: db = new DataBufferByte(buf.array(), r.area()); break; case DataBuffer.TYPE_SHORT: dst = ShortBuffer.allocate(r.area()).put(buf.asShortBuffer()); db = new DataBufferShort(((ShortBuffer)dst).array(), r.area()); break; } assert(db != null); return Raster.createRaster(sm, db, new java.awt.Point(0, 0)); } private static HashMap converters; public int bpp; public int depth; public boolean bigEndian; public boolean trueColour; public int redMax; public int greenMax; public int blueMax; public int redShift; public int greenShift; public int blueShift; protected int redBits, greenBits, blueBits; protected int maxBits, minBits; protected boolean endianMismatch; private ColorModel model; }