| /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. |
| * Copyright (C) 2011 D. R. Commander. All Rights Reserved. |
| * Copyright 2009-2014 Pierre Ossman for Cendio AB |
| * |
| * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| * USA. |
| */ |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <rdr/InStream.h> |
| #include <rdr/OutStream.h> |
| #include <rfb/Exception.h> |
| #include <rfb/PixelFormat.h> |
| #include <rfb/util.h> |
| |
| #ifdef _WIN32 |
| #define strcasecmp _stricmp |
| #endif |
| |
| using namespace rfb; |
| |
| rdr::U8 PixelFormat::upconvTable[256*8]; |
| rdr::U8 PixelFormat::downconvTable[256*8]; |
| |
| class PixelFormat::Init { |
| public: |
| Init(); |
| }; |
| |
| PixelFormat::Init PixelFormat::_init; |
| |
| |
| PixelFormat::Init::Init() |
| { |
| int bits; |
| |
| // Shifting bits is almost perfect, but not quite. And |
| // a lookup table is still quicker when there is a large |
| // difference between the source and destination depth. |
| |
| for (bits = 1;bits <= 8;bits++) { |
| int i, maxVal; |
| rdr::U8 *subUpTable; |
| rdr::U8 *subDownTable; |
| |
| maxVal = (1 << bits) - 1; |
| subUpTable = &upconvTable[(bits-1)*256]; |
| subDownTable = &downconvTable[(bits-1)*256]; |
| |
| for (i = 0;i <= maxVal;i++) |
| subUpTable[i] = i * 255 / maxVal; |
| |
| // Duplicate the up table so that we don't have to care about |
| // the upper bits when doing a lookup |
| for (;i < 256;i += maxVal+1) |
| memcpy(&subUpTable[i], &subUpTable[0], maxVal+1); |
| |
| for (i = 0;i <= 255;i++) |
| subDownTable[i] = (i * maxVal + 128) / 255; |
| } |
| } |
| |
| |
| PixelFormat::PixelFormat(int b, int d, bool e, bool 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) |
| { |
| assert(isSane()); |
| |
| updateState(); |
| } |
| |
| PixelFormat::PixelFormat() |
| : bpp(8), depth(8), trueColour(true), bigEndian(false), |
| redMax(7), greenMax(7), blueMax(3), |
| redShift(0), greenShift(3), blueShift(6) |
| { |
| updateState(); |
| } |
| |
| bool PixelFormat::equal(const PixelFormat& other) const |
| { |
| 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; |
| } |
| |
| void PixelFormat::read(rdr::InStream* is) |
| { |
| bpp = is->readU8(); |
| depth = is->readU8(); |
| bigEndian = is->readU8(); |
| trueColour = is->readU8(); |
| 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 Exception("invalid pixel format"); |
| |
| updateState(); |
| } |
| |
| void PixelFormat::write(rdr::OutStream* os) const |
| { |
| os->writeU8(bpp); |
| os->writeU8(depth); |
| os->writeU8(bigEndian); |
| os->writeU8(trueColour); |
| os->writeU16(redMax); |
| os->writeU16(greenMax); |
| os->writeU16(blueMax); |
| os->writeU8(redShift); |
| os->writeU8(greenShift); |
| os->writeU8(blueShift); |
| os->pad(3); |
| } |
| |
| |
| bool PixelFormat::is888(void) const |
| { |
| 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; |
| |
| return true; |
| } |
| |
| |
| bool PixelFormat::isBigEndian(void) const |
| { |
| return bigEndian; |
| } |
| |
| |
| bool PixelFormat::isLittleEndian(void) const |
| { |
| return ! bigEndian; |
| } |
| |
| |
| void PixelFormat::bufferFromRGB(rdr::U8 *dst, const rdr::U8* src, int pixels) const |
| { |
| bufferFromRGB(dst, src, pixels, pixels, 1); |
| } |
| |
| void PixelFormat::bufferFromRGB(rdr::U8 *dst, const rdr::U8* src, |
| int w, int stride, int h) const |
| { |
| if (is888()) { |
| // Optimised common case |
| rdr::U8 *r, *g, *b, *x; |
| |
| if (bigEndian) { |
| r = dst + (24 - redShift)/8; |
| g = dst + (24 - greenShift)/8; |
| b = dst + (24 - blueShift)/8; |
| x = dst + (24 - (48 - redShift - greenShift - blueShift))/8; |
| } else { |
| r = dst + redShift/8; |
| g = dst + greenShift/8; |
| b = dst + blueShift/8; |
| x = dst + (48 - redShift - greenShift - blueShift)/8; |
| } |
| |
| int dstPad = (stride - w) * 4; |
| while (h--) { |
| int w_ = w; |
| while (w_--) { |
| *r = *(src++); |
| *g = *(src++); |
| *b = *(src++); |
| *x = 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--) { |
| int w_ = w; |
| while (w_--) { |
| Pixel p; |
| rdr::U8 r, g, b; |
| |
| r = *(src++); |
| g = *(src++); |
| b = *(src++); |
| |
| p = pixelFromRGB(r, g, b); |
| |
| bufferFromPixel(dst, p); |
| dst += bpp/8; |
| } |
| dst += dstPad; |
| } |
| } |
| } |
| |
| |
| void PixelFormat::rgbFromBuffer(rdr::U8* dst, const rdr::U8* src, int pixels) const |
| { |
| rgbFromBuffer(dst, src, pixels, pixels, 1); |
| } |
| |
| |
| void PixelFormat::rgbFromBuffer(rdr::U8* dst, const rdr::U8* src, |
| int w, int stride, int h) const |
| { |
| if (is888()) { |
| // Optimised common case |
| const rdr::U8 *r, *g, *b; |
| |
| if (bigEndian) { |
| r = src + (24 - redShift)/8; |
| g = src + (24 - greenShift)/8; |
| b = src + (24 - blueShift)/8; |
| } else { |
| r = src + redShift/8; |
| g = src + greenShift/8; |
| b = src + blueShift/8; |
| } |
| |
| int srcPad = (stride - w) * 4; |
| while (h--) { |
| int w_ = w; |
| while (w_--) { |
| *(dst++) = *r; |
| *(dst++) = *g; |
| *(dst++) = *b; |
| r += 4; |
| g += 4; |
| b += 4; |
| } |
| r += srcPad; |
| g += srcPad; |
| b += srcPad; |
| } |
| } else { |
| // Generic code |
| int srcPad = (stride - w) * bpp/8; |
| while (h--) { |
| int w_ = w; |
| while (w_--) { |
| Pixel p; |
| rdr::U8 r, g, b; |
| |
| p = pixelFromBuffer(src); |
| |
| rgbFromPixel(p, &r, &g, &b); |
| |
| *(dst++) = r; |
| *(dst++) = g; |
| *(dst++) = b; |
| src += bpp/8; |
| } |
| src += srcPad; |
| } |
| } |
| } |
| |
| |
| Pixel PixelFormat::pixelFromPixel(const PixelFormat &srcPF, Pixel src) const |
| { |
| rdr::U16 r, g, b; |
| srcPF.rgbFromPixel(src, &r, &g, &b); |
| return pixelFromRGB(r, g, b); |
| } |
| |
| |
| void PixelFormat::bufferFromBuffer(rdr::U8* dst, const PixelFormat &srcPF, |
| const rdr::U8* src, int pixels) const |
| { |
| bufferFromBuffer(dst, srcPF, src, pixels, 1, pixels, pixels); |
| } |
| |
| #define IS_ALIGNED(v, a) (((intptr_t)v & (a-1)) == 0) |
| |
| void PixelFormat::bufferFromBuffer(rdr::U8* dst, const PixelFormat &srcPF, |
| const rdr::U8* src, int w, int h, |
| int dstStride, int srcStride) const |
| { |
| if (equal(srcPF)) { |
| // Trivial case |
| while (h--) { |
| memcpy(dst, src, w * bpp/8); |
| dst += dstStride * bpp/8; |
| src += srcStride * srcPF.bpp/8; |
| } |
| } else if (is888() && srcPF.is888()) { |
| // Optimised common case A: byte shuffling (e.g. endian conversion) |
| rdr::U8 *d[4], *s[4]; |
| int dstPad, srcPad; |
| |
| if (bigEndian) { |
| s[0] = dst + (24 - redShift)/8; |
| s[1] = dst + (24 - greenShift)/8; |
| s[2] = dst + (24 - blueShift)/8; |
| s[3] = dst + (24 - (48 - redShift - greenShift - blueShift))/8; |
| } else { |
| s[0] = dst + redShift/8; |
| s[1] = dst + greenShift/8; |
| s[2] = dst + blueShift/8; |
| s[3] = dst + (48 - redShift - greenShift - blueShift)/8; |
| } |
| |
| if (srcPF.bigEndian) { |
| d[(24 - srcPF.redShift)/8] = s[0]; |
| d[(24 - srcPF.greenShift)/8] = s[1]; |
| d[(24 - srcPF.blueShift)/8] = s[2]; |
| d[(24 - (48 - srcPF.redShift - srcPF.greenShift - srcPF.blueShift))/8] = s[3]; |
| } else { |
| d[srcPF.redShift/8] = s[0]; |
| d[srcPF.greenShift/8] = s[1]; |
| d[srcPF.blueShift/8] = s[2]; |
| d[(48 - srcPF.redShift - srcPF.greenShift - srcPF.blueShift)/8] = s[3]; |
| } |
| |
| dstPad = (dstStride - w) * 4; |
| srcPad = (srcStride - w) * 4; |
| while (h--) { |
| int w_ = w; |
| while (w_--) { |
| *d[0] = *(src++); |
| *d[1] = *(src++); |
| *d[2] = *(src++); |
| *d[3] = *(src++); |
| d[0] += 4; |
| d[1] += 4; |
| d[2] += 4; |
| d[3] += 4; |
| } |
| d[0] += dstPad; |
| d[1] += dstPad; |
| d[2] += dstPad; |
| d[3] += dstPad; |
| src += srcPad; |
| } |
| } else if (IS_ALIGNED(dst, bpp/8) && srcPF.is888()) { |
| // Optimised common case B: 888 source |
| switch (bpp) { |
| case 8: |
| directBufferFromBufferFrom888((rdr::U8*)dst, srcPF, src, |
| w, h, dstStride, srcStride); |
| break; |
| case 16: |
| directBufferFromBufferFrom888((rdr::U16*)dst, srcPF, src, |
| w, h, dstStride, srcStride); |
| break; |
| case 32: |
| directBufferFromBufferFrom888((rdr::U32*)dst, srcPF, src, |
| w, h, dstStride, srcStride); |
| break; |
| } |
| } else if (IS_ALIGNED(src, srcPF.bpp/8) && is888()) { |
| // Optimised common case C: 888 destination |
| switch (srcPF.bpp) { |
| case 8: |
| directBufferFromBufferTo888(dst, srcPF, (rdr::U8*)src, |
| w, h, dstStride, srcStride); |
| break; |
| case 16: |
| directBufferFromBufferTo888(dst, srcPF, (rdr::U16*)src, |
| w, h, dstStride, srcStride); |
| break; |
| case 32: |
| directBufferFromBufferTo888(dst, srcPF, (rdr::U32*)src, |
| w, h, dstStride, srcStride); |
| break; |
| } |
| } else { |
| // Generic code |
| int dstPad = (dstStride - w) * bpp/8; |
| int srcPad = (srcStride - w) * srcPF.bpp/8; |
| while (h--) { |
| int w_ = w; |
| while (w_--) { |
| Pixel p; |
| rdr::U8 r, g, b; |
| |
| p = srcPF.pixelFromBuffer(src); |
| srcPF.rgbFromPixel(p, &r, &g, &b); |
| p = pixelFromRGB(r, g, b); |
| bufferFromPixel(dst, p); |
| |
| dst += bpp/8; |
| src += srcPF.bpp/8; |
| } |
| dst += dstPad; |
| src += srcPad; |
| } |
| } |
| } |
| |
| |
| void PixelFormat::print(char* str, int len) const |
| { |
| // Unfortunately snprintf is not widely available so we build the string up |
| // using strncat - not pretty, but should be safe against buffer overruns. |
| |
| char num[20]; |
| if (len < 1) return; |
| str[0] = 0; |
| strncat(str, "depth ", len-1-strlen(str)); |
| sprintf(num,"%d",depth); |
| strncat(str, num, len-1-strlen(str)); |
| strncat(str, " (", len-1-strlen(str)); |
| sprintf(num,"%d",bpp); |
| strncat(str, num, len-1-strlen(str)); |
| strncat(str, "bpp)", len-1-strlen(str)); |
| if (bpp != 8) { |
| if (bigEndian) |
| strncat(str, " big-endian", len-1-strlen(str)); |
| else |
| strncat(str, " little-endian", len-1-strlen(str)); |
| } |
| |
| if (!trueColour) { |
| strncat(str, " color-map", len-1-strlen(str)); |
| return; |
| } |
| |
| if (blueShift == 0 && greenShift > blueShift && redShift > greenShift && |
| blueMax == (1 << greenShift) - 1 && |
| greenMax == (1 << (redShift-greenShift)) - 1 && |
| redMax == (1 << (depth-redShift)) - 1) |
| { |
| strncat(str, " rgb", len-1-strlen(str)); |
| sprintf(num,"%d",depth-redShift); |
| strncat(str, num, len-1-strlen(str)); |
| sprintf(num,"%d",redShift-greenShift); |
| strncat(str, num, len-1-strlen(str)); |
| sprintf(num,"%d",greenShift); |
| strncat(str, num, len-1-strlen(str)); |
| return; |
| } |
| |
| if (redShift == 0 && greenShift > redShift && blueShift > greenShift && |
| redMax == (1 << greenShift) - 1 && |
| greenMax == (1 << (blueShift-greenShift)) - 1 && |
| blueMax == (1 << (depth-blueShift)) - 1) |
| { |
| strncat(str, " bgr", len-1-strlen(str)); |
| sprintf(num,"%d",depth-blueShift); |
| strncat(str, num, len-1-strlen(str)); |
| sprintf(num,"%d",blueShift-greenShift); |
| strncat(str, num, len-1-strlen(str)); |
| sprintf(num,"%d",greenShift); |
| strncat(str, num, len-1-strlen(str)); |
| return; |
| } |
| |
| strncat(str, " rgb max ", len-1-strlen(str)); |
| sprintf(num,"%d,",redMax); |
| strncat(str, num, len-1-strlen(str)); |
| sprintf(num,"%d,",greenMax); |
| strncat(str, num, len-1-strlen(str)); |
| sprintf(num,"%d",blueMax); |
| strncat(str, num, len-1-strlen(str)); |
| strncat(str, " shift ", len-1-strlen(str)); |
| sprintf(num,"%d,",redShift); |
| strncat(str, num, len-1-strlen(str)); |
| sprintf(num,"%d,",greenShift); |
| strncat(str, num, len-1-strlen(str)); |
| sprintf(num,"%d",blueShift); |
| strncat(str, num, len-1-strlen(str)); |
| } |
| |
| |
| bool PixelFormat::parse(const char* str) |
| { |
| char rgbbgr[4]; |
| int bits1, bits2, bits3; |
| if (sscanf(str, "%3s%1d%1d%1d", rgbbgr, &bits1, &bits2, &bits3) < 4) |
| return false; |
| |
| depth = bits1 + bits2 + bits3; |
| bpp = depth <= 8 ? 8 : ((depth <= 16) ? 16 : 32); |
| trueColour = true; |
| rdr::U32 endianTest = 1; |
| bigEndian = (*(rdr::U8*)&endianTest == 0); |
| |
| greenShift = bits3; |
| greenMax = (1 << bits2) - 1; |
| |
| if (strcasecmp(rgbbgr, "bgr") == 0) { |
| redShift = 0; |
| redMax = (1 << bits3) - 1; |
| blueShift = bits3 + bits2; |
| blueMax = (1 << bits1) - 1; |
| } else if (strcasecmp(rgbbgr, "rgb") == 0) { |
| blueShift = 0; |
| blueMax = (1 << bits3) - 1; |
| redShift = bits3 + bits2; |
| redMax = (1 << bits1) - 1; |
| } else { |
| return false; |
| } |
| |
| assert(isSane()); |
| |
| updateState(); |
| |
| return true; |
| } |
| |
| |
| static int bits(rdr::U16 value) |
| { |
| int bits; |
| |
| bits = 16; |
| |
| if (!(value & 0xff00)) { |
| bits -= 8; |
| value <<= 8; |
| } |
| if (!(value & 0xf000)) { |
| bits -= 4; |
| value <<= 4; |
| } |
| if (!(value & 0xc000)) { |
| bits -= 2; |
| value <<= 2; |
| } |
| if (!(value & 0x8000)) { |
| bits -= 1; |
| value <<= 1; |
| } |
| |
| return bits; |
| } |
| |
| void PixelFormat::updateState(void) |
| { |
| 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; |
| } |
| |
| bool PixelFormat::isSane(void) |
| { |
| 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 > 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; |
| } |
| |
| // Preprocessor generated, optimised methods |
| |
| #define INBPP 8 |
| #define OUTBPP 8 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #define OUTBPP 16 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #define OUTBPP 32 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #undef INBPP |
| |
| #define INBPP 16 |
| #define OUTBPP 8 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #define OUTBPP 16 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #define OUTBPP 32 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #undef INBPP |
| |
| #define INBPP 32 |
| #define OUTBPP 8 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #define OUTBPP 16 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #define OUTBPP 32 |
| #include "PixelFormatBPP.cxx" |
| #undef OUTBPP |
| #undef INBPP |
| |