diff options
author | Pierre Ossman <ossman@cendio.se> | 2017-02-19 15:48:17 +0100 |
---|---|---|
committer | Pierre Ossman <ossman@cendio.se> | 2017-02-22 16:58:10 +0100 |
commit | 6a1a0d0c578e39338e757edf59cf8806a9d86b0f (patch) | |
tree | a82ede30f7e9d1f36b3169acb6a7a6a4f936ad78 | |
parent | e20cf62bbdefd48979603105d60c3a170eb2ece6 (diff) | |
download | tigervnc-6a1a0d0c578e39338e757edf59cf8806a9d86b0f.tar.gz tigervnc-6a1a0d0c578e39338e757edf59cf8806a9d86b0f.zip |
Change cursor API to use RGBA data
This will allow us to use better formats that preserve the entire
alpha channel.
-rw-r--r-- | common/rfb/CMsgHandler.h | 2 | ||||
-rw-r--r-- | common/rfb/CMsgReader.cxx | 29 | ||||
-rw-r--r-- | common/rfb/ConnParams.cxx | 15 | ||||
-rw-r--r-- | common/rfb/ConnParams.h | 4 | ||||
-rw-r--r-- | common/rfb/Cursor.cxx | 203 | ||||
-rw-r--r-- | common/rfb/Cursor.h | 35 | ||||
-rw-r--r-- | common/rfb/PixelBuffer.cxx | 104 | ||||
-rw-r--r-- | common/rfb/PixelBuffer.h | 14 | ||||
-rw-r--r-- | common/rfb/SMsgWriter.cxx | 56 | ||||
-rw-r--r-- | common/rfb/SMsgWriter.h | 1 | ||||
-rw-r--r-- | common/rfb/VNCSConnectionST.cxx | 2 | ||||
-rw-r--r-- | common/rfb/VNCServer.h | 12 | ||||
-rw-r--r-- | common/rfb/VNCServerST.cxx | 24 | ||||
-rw-r--r-- | common/rfb/VNCServerST.h | 4 | ||||
-rw-r--r-- | tests/decperf.cxx | 4 | ||||
-rw-r--r-- | tests/encperf.cxx | 4 | ||||
-rw-r--r-- | unix/xserver/hw/vnc/XserverDesktop.cc | 33 | ||||
-rw-r--r-- | vncviewer/CConn.cxx | 4 | ||||
-rw-r--r-- | vncviewer/CConn.h | 2 | ||||
-rw-r--r-- | vncviewer/DesktopWindow.cxx | 4 | ||||
-rw-r--r-- | vncviewer/DesktopWindow.h | 2 | ||||
-rw-r--r-- | vncviewer/Viewport.cxx | 42 | ||||
-rw-r--r-- | vncviewer/Viewport.h | 2 | ||||
-rw-r--r-- | win/rfb_win32/DeviceFrameBuffer.cxx | 237 | ||||
-rw-r--r-- | win/rfb_win32/DeviceFrameBuffer.h | 2 |
25 files changed, 374 insertions, 467 deletions
diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h index 7d2cdc20..2686712e 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -50,7 +50,7 @@ namespace rfb { int w, int h, const ScreenSet& layout); virtual void setCursor(int width, int height, const Point& hotspot, - void* data, void* mask) = 0; + const rdr::U8* data) = 0; virtual void setPixelFormat(const PixelFormat& pf); virtual void setName(const char* name); virtual void fence(rdr::U32 flags, unsigned len, const char data[]); diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx index 96ddf443..bbb99096 100644 --- a/common/rfb/CMsgReader.cxx +++ b/common/rfb/CMsgReader.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-2017 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 @@ -198,10 +198,35 @@ void CMsgReader::readSetCursor(int width, int height, const Point& hotspot) rdr::U8Array data(data_len); rdr::U8Array mask(mask_len); + int x, y; + rdr::U8 buf[width*height*4]; + rdr::U8* in; + rdr::U8* out; + is->readBytes(data.buf, data_len); is->readBytes(mask.buf, mask_len); - handler->setCursor(width, height, hotspot, data.buf, mask.buf); + int maskBytesPerRow = (width+7)/8; + in = data.buf; + out = buf; + for (y = 0;y < height;y++) { + for (x = 0;x < width;x++) { + int byte = y * maskBytesPerRow + x / 8; + int bit = 7 - x % 8; + + handler->cp.pf().rgbFromBuffer(out, in, 1); + + if (mask.buf[byte] & (1 << bit)) + out[3] = 255; + else + out[3] = 0; + + in += handler->cp.pf().bpp/8; + out += 4; + } + } + + handler->setCursor(width, height, hotspot, buf); } void CMsgReader::readSetDesktopName(int x, int y, int w, int h) diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index ab3b884a..dc9ebf58 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -39,11 +39,13 @@ ConnParams::ConnParams() subsampling(subsampleUndefined), name_(0), verStrPos(0) { setName(""); + cursor_ = new Cursor(0, 0, Point(), NULL); } ConnParams::~ConnParams() { delete [] name_; + delete cursor_; } bool ConnParams::readVersion(rdr::InStream* is, bool* done) @@ -86,17 +88,8 @@ void ConnParams::setName(const char* name) void ConnParams::setCursor(const Cursor& other) { - const rdr::U8* data; - int stride; - - cursor_.hotspot = other.hotspot; - cursor_.setPF(other.getPF()); - cursor_.setSize(other.width(), other.height()); - - data = other.getBuffer(other.getRect(), &stride); - cursor_.imageRect(cursor_.getRect(), data, stride); - - memcpy(cursor_.mask.buf, other.mask.buf, cursor_.maskLen()); + delete cursor_; + cursor_ = new Cursor(other); } bool ConnParams::supportsEncoding(rdr::S32 encoding) const diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h index 9e647baa..517e6490 100644 --- a/common/rfb/ConnParams.h +++ b/common/rfb/ConnParams.h @@ -77,7 +77,7 @@ namespace rfb { const char* name() const { return name_; } void setName(const char* name); - const Cursor& cursor() const { return cursor_; } + const Cursor& cursor() const { return *cursor_; } void setCursor(const Cursor& cursor); bool supportsEncoding(rdr::S32 encoding) const; @@ -106,7 +106,7 @@ namespace rfb { PixelFormat pf_; char* name_; - Cursor cursor_; + Cursor* cursor_; std::set<rdr::S32> encodings_; char verStr[13]; int verStrPos; diff --git a/common/rfb/Cursor.cxx b/common/rfb/Cursor.cxx index e226118c..a79f0460 100644 --- a/common/rfb/Cursor.cxx +++ b/common/rfb/Cursor.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2014 Pierre Ossman for Cendio AB + * Copyright 2014-2017 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 @@ -26,71 +26,63 @@ using namespace rfb; static LogWriter vlog("Cursor"); -void Cursor::setSize(int w, int h) { - int oldMaskLen = maskLen(); - ManagedPixelBuffer::setSize(w, h); - if (maskLen() > oldMaskLen) { - delete [] mask.buf; - mask.buf = new rdr::U8[maskLen()]; - } +Cursor::Cursor(int width, int height, const Point& hotspot, + const rdr::U8* data) : + width_(width), height_(height), hotspot_(hotspot) +{ + this->data = new rdr::U8[width_*height_*4]; + memcpy(this->data, data, width_*height_*4); } -void Cursor::drawOutline(const Pixel& c) +Cursor::Cursor(const Cursor& other) : + width_(other.width_), height_(other.height_), + hotspot_(other.hotspot_) { - Cursor outlined; - rdr::U8 cbuf[4]; - - // Create a mirror of the existing cursor - outlined.setPF(getPF()); - outlined.setSize(width(), height()); - outlined.hotspot = hotspot; + data = new rdr::U8[width_*height_*4]; + memcpy(data, other.data, width_*height_*4); +} - // Clear the mirror's background to the outline colour - outlined.getPF().bufferFromPixel(cbuf, c); - outlined.fillRect(getRect(), cbuf); +Cursor::~Cursor() +{ + delete [] data; +} - // Blit the existing cursor, using its mask - outlined.maskRect(getRect(), data, mask.buf); +rdr::U8* Cursor::getBitmap() const +{ + rdr::U8Array source((width()+7)/8*height()); + memset(source.buf, 0, (width()+7)/8*height()); - // Now just adjust the mask to add the outline. The outline pixels - // will already be the right colour. :) int maskBytesPerRow = (width() + 7) / 8; + const rdr::U8 *data_ptr = data; for (int y = 0; y < height(); y++) { - for (int byte=0; byte<maskBytesPerRow; byte++) { - rdr::U8 m8 = mask.buf[y*maskBytesPerRow + byte]; - - // Handle above & below outline - if (y > 0) m8 |= mask.buf[(y-1)*maskBytesPerRow + byte]; - if (y < height()-1) m8 |= mask.buf[(y+1)*maskBytesPerRow + byte]; - - // Left outline - m8 |= mask.buf[y*maskBytesPerRow + byte] << 1; - if (byte < maskBytesPerRow-1) - m8 |= (mask.buf[y*maskBytesPerRow + byte + 1] >> 7) & 1; - - // Right outline - m8 |= mask.buf[y*maskBytesPerRow + byte] >> 1; - if (byte > 0) - m8 |= (mask.buf[y*maskBytesPerRow + byte - 1] << 7) & 128; - - outlined.mask.buf[y*maskBytesPerRow + byte] = m8; + for (int x = 0; x < width(); x++) { + int byte = y * maskBytesPerRow + x / 8; + int bit = 7 - x % 8; + if (data_ptr[3] >= 0x80) { + // Use Luma with BT.709 coefficients for grayscale + unsigned luma; + + luma = 0; + luma += (unsigned)data_ptr[0] * 13933; // 0.2126 + luma += (unsigned)data_ptr[1] * 46871; // 0.7152 + luma += (unsigned)data_ptr[2] * 4732; // 0.0722 + luma /= 65536; + + // Gamma compensated half intensity gray + if (luma > 187) + source.buf[byte] |= (1 << bit); + } + data_ptr += 4; } } - // Replace the existing cursor & mask with the new one - delete [] data; - delete [] mask.buf; - data = outlined.data; outlined.data = 0; - mask.buf = outlined.mask.buf; outlined.mask.buf = 0; + return source.takeBuf(); } -rdr::U8* Cursor::getBitmap(Pixel* pix0, Pixel* pix1) const +rdr::U8* Cursor::getMask() const { - bool gotPix0 = false; - bool gotPix1 = false; - *pix0 = *pix1 = 0; - rdr::U8Array source(maskLen()); - memset(source.buf, 0, maskLen()); + rdr::U8Array mask((width()+7)/8*height()); + memset(mask.buf, 0, (width()+7)/8*height()); int maskBytesPerRow = (width() + 7) / 8; const rdr::U8 *data_ptr = data; @@ -98,24 +90,13 @@ rdr::U8* Cursor::getBitmap(Pixel* pix0, Pixel* pix1) const for (int x = 0; x < width(); x++) { int byte = y * maskBytesPerRow + x / 8; int bit = 7 - x % 8; - if (mask.buf[byte] & (1 << bit)) { - Pixel pix = getPF().pixelFromBuffer(data_ptr); - if (!gotPix0 || pix == *pix0) { - gotPix0 = true; - *pix0 = pix; - } else if (!gotPix1 || pix == *pix1) { - gotPix1 = true; - *pix1 = pix; - source.buf[byte] |= (1 << bit); - } else { - // not a bitmap - return 0; - } - } - data_ptr += getPF().bpp/8; + if (data_ptr[3] >= 0x80) + mask.buf[byte] |= (1 << bit); + data_ptr += 4; } } - return source.takeBuf(); + + return mask.takeBuf(); } // crop() determines the "busy" rectangle for the cursor - the minimum bounding @@ -126,58 +107,40 @@ rdr::U8* Cursor::getBitmap(Pixel* pix0, Pixel* pix1) const void Cursor::crop() { - Rect busy = getRect().intersect(Rect(hotspot.x, hotspot.y, - hotspot.x+1, hotspot.y+1)); - int maskBytesPerRow = (width() + 7) / 8; + Rect busy = Rect(0, 0, width_, height_); + busy = busy.intersect(Rect(hotspot_.x, hotspot_.y, + hotspot_.x+1, hotspot_.y+1)); int x, y; + rdr::U8 *data_ptr = data; for (y = 0; y < height(); y++) { for (x = 0; x < width(); x++) { - int byte = y * maskBytesPerRow + x / 8; - int bit = 7 - x % 8; - if (mask.buf[byte] & (1 << bit)) { + if (data_ptr[3] > 0) { if (x < busy.tl.x) busy.tl.x = x; if (x+1 > busy.br.x) busy.br.x = x+1; if (y < busy.tl.y) busy.tl.y = y; if (y+1 > busy.br.y) busy.br.y = y+1; } + data_ptr += 4; } } if (width() == busy.width() && height() == busy.height()) return; - vlog.debug("cropping %dx%d to %dx%d", width(), height(), - busy.width(), busy.height()); - // Copy the pixel data - int newDataLen = busy.area() * (getPF().bpp/8); + int newDataLen = busy.area() * 4; rdr::U8* newData = new rdr::U8[newDataLen]; - getImage(newData, busy); - - // Copy the mask - int newMaskBytesPerRow = (busy.width()+7)/8; - int newMaskLen = newMaskBytesPerRow * busy.height(); - rdr::U8* newMask = new rdr::U8[newMaskLen]; - memset(newMask, 0, newMaskLen); - for (y = 0; y < busy.height(); y++) { - int newByte, newBit; - for (x = 0; x < busy.width(); x++) { - int oldByte = (y+busy.tl.y) * maskBytesPerRow + (x+busy.tl.x) / 8; - int oldBit = 7 - (x+busy.tl.x) % 8; - newByte = y * newMaskBytesPerRow + x / 8; - newBit = 7 - x % 8; - if (mask.buf[oldByte] & (1 << oldBit)) - newMask[newByte] |= (1 << newBit); - } + data_ptr = newData; + for (y = busy.tl.y; y < busy.br.y; y++) { + memcpy(data_ptr, data + y*width()*4 + busy.tl.x*4, busy.width()*4); + data_ptr += busy.width()*4; } // Set the size and data to the new, cropped cursor. - setSize(busy.width(), busy.height()); - hotspot = hotspot.subtract(busy.tl); + width_ = busy.width(); + height_ = busy.height(); + hotspot_ = hotspot_.subtract(busy.tl); delete [] data; - delete [] mask.buf; - datasize = newDataLen; data = newData; - mask.buf = newMask; } RenderedCursor::RenderedCursor() @@ -207,26 +170,48 @@ void RenderedCursor::update(PixelBuffer* framebuffer, assert(framebuffer); assert(cursor); - if (!framebuffer->getPF().equal(cursor->getPF())) - throw Exception("RenderedCursor: Trying to render cursor on incompatible frame buffer"); - format = framebuffer->getPF(); width_ = framebuffer->width(); height_ = framebuffer->height(); - rawOffset = pos.subtract(cursor->hotspot); - clippedRect = cursor->getRect(rawOffset).intersect(framebuffer->getRect()); + rawOffset = pos.subtract(cursor->hotspot()); + clippedRect = Rect(0, 0, cursor->width(), cursor->height()) + .translate(rawOffset) + .intersect(framebuffer->getRect()); offset = clippedRect.tl; - buffer.setPF(cursor->getPF()); + buffer.setPF(format); buffer.setSize(clippedRect.width(), clippedRect.height()); data = framebuffer->getBuffer(buffer.getRect(offset), &stride); buffer.imageRect(buffer.getRect(), data, stride); diff = offset.subtract(rawOffset); - data = cursor->getBuffer(buffer.getRect(diff), &stride); + for (int y = 0;y < buffer.height();y++) { + for (int x = 0;x < buffer.width();x++) { + size_t idx; + rdr::U8 bg[4], fg[4]; + rdr::U8 rgb[3]; + + idx = (y+diff.y)*cursor->width() + (x+diff.x); + memcpy(fg, cursor->getBuffer() + idx*4, 4); + + if (fg[3] == 0x00) + continue; + else if (fg[3] == 0xff) { + memcpy(rgb, fg, 3); + } else { + buffer.getImage(bg, Rect(x, y, x+1, y+1)); + format.rgbFromBuffer(rgb, bg, 1); + // FIXME: Gamma aware blending + for (int i = 0;i < 3;i++) { + rgb[i] = (unsigned)rgb[i]*(255-fg[3])/255 + + (unsigned)fg[i]*fg[3]/255; + } + } - buffer.maskRect(buffer.getRect(), data, cursor->mask.buf, diff, - stride, (cursor->width() + 7) / 8); + format.bufferFromRGB(bg, rgb, 1); + buffer.imageRect(Rect(x, y, x+1, y+1), bg); + } + } } diff --git a/common/rfb/Cursor.h b/common/rfb/Cursor.h index 560e4d81..6c6db7ee 100644 --- a/common/rfb/Cursor.h +++ b/common/rfb/Cursor.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2014 Pierre Ossman for Cendio AB + * Copyright 2014-2017 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 @@ -28,29 +28,30 @@ namespace rfb { - class Cursor : public ManagedPixelBuffer { + class Cursor { public: - Cursor() {} - rdr::U8Array mask; - Point hotspot; + Cursor(int width, int height, const Point& hotspot, const rdr::U8* data); + Cursor(const Cursor& other); + ~Cursor(); - int maskLen() const { return (width() + 7) / 8 * height(); } + int width() const { return width_; }; + int height() const { return height_; }; + const Point& hotspot() const { return hotspot_; }; + const rdr::U8* getBuffer() const { return data; }; - // setSize() resizes the cursor. The contents of the data and mask are - // undefined after this call. - virtual void setSize(int w, int h); - - // drawOutline() adds an outline to the cursor in the given colour. - void drawOutline(const Pixel& c); - - // getBitmap() tests whether the cursor is monochrome, and if so returns a - // bitmap together with background and foreground colours. The size and - // layout of the bitmap are the same as the mask. - rdr::U8* getBitmap(Pixel* pix0, Pixel* pix1) const; + // getBitmap() returns a monochrome version of the cursor + rdr::U8* getBitmap() const; + // getMask() returns a simple mask version of the alpha channel + rdr::U8* getMask() const; // crop() crops the cursor down to the smallest possible size, based on the // mask. void crop(); + + protected: + int width_, height_; + Point hotspot_; + rdr::U8* data; }; class RenderedCursor : public PixelBuffer { diff --git a/common/rfb/PixelBuffer.cxx b/common/rfb/PixelBuffer.cxx index e788fcad..1edd9c3d 100644 --- a/common/rfb/PixelBuffer.cxx +++ b/common/rfb/PixelBuffer.cxx @@ -200,110 +200,6 @@ void ModifiablePixelBuffer::imageRect(const Rect& r, commitBufferRW(r); } -void ModifiablePixelBuffer::maskRect(const Rect& r, - const void* pixels, - const void* mask_, - const Point& maskPos, - int pStride, int mStride) -{ - int stride; - U8* data; - U8* mask; - int w, h, bpp; - - if (!r.enclosed_by(getRect())) - throw rfb::Exception("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d", - r.width(), r.height(), - r.tl.x, r.tl.y, width_, height_); - - data = getBufferRW(r, &stride); - mask = (U8*) mask_; - - w = r.width(); - h = r.height(); - bpp = getPF().bpp; - if (pStride == 0) - pStride = r.width(); - if (mStride == 0) - mStride = (r.width() + 7) / 8; - - mask += maskPos.y * mStride; - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int cx = maskPos.x + x; - U8* byte = mask + (cx / 8); - int bit = 7 - cx % 8; - if ((*byte) & (1 << bit)) { - switch (bpp) { - case 8: - ((U8*)data)[y * stride + x] = ((U8*)pixels)[y * pStride + x]; - break; - case 16: - ((U16*)data)[y * stride + x] = ((U16*)pixels)[y * pStride + x]; - break; - case 32: - ((U32*)data)[y * stride + x] = ((U32*)pixels)[y * pStride + x]; - break; - } - } - } - mask += mStride; - } - - commitBufferRW(r); -} - -void ModifiablePixelBuffer::maskRect(const Rect& r, - Pixel pixel, - const void* mask_, - const Point& maskPos, - int mStride) -{ - int stride; - U8* data; - U8* mask; - int w, h, bpp; - - if (!r.enclosed_by(getRect())) - throw rfb::Exception("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d", - r.width(), r.height(), - r.tl.x, r.tl.y, width_, height_); - - data = getBufferRW(r, &stride); - mask = (U8*) mask_; - - w = r.width(); - h = r.height(); - bpp = getPF().bpp; - if (mStride == 0) - mStride = (r.width() + 7) / 8; - - mask += maskPos.y * mStride; - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int cx = maskPos.x + x; - U8* byte = mask + (cx / 8); - int bit = 7 - cx % 8; - if ((*byte) & (1 << bit)) { - switch (bpp) { - case 8: - ((U8*)data)[y * stride + x] = pixel; - break; - case 16: - ((U16*)data)[y * stride + x] = pixel; - break; - case 32: - ((U32*)data)[y * stride + x] = pixel; - break; - } - } - } - mask += mStride; - } - - commitBufferRW(r); -} - void ModifiablePixelBuffer::copyRect(const Rect &rect, const Point &move_by_delta) { diff --git a/common/rfb/PixelBuffer.h b/common/rfb/PixelBuffer.h index b38999ae..75caa63f 100644 --- a/common/rfb/PixelBuffer.h +++ b/common/rfb/PixelBuffer.h @@ -127,20 +127,6 @@ namespace rfb { // Copy pixel data from one PixelBuffer location to another void copyRect(const Rect &dest, const Point& move_by_delta); - // Copy pixel data to the buffer through a mask - // pixels is a pointer to the pixel to be copied to r.tl. - // maskPos specifies the pixel offset in the mask to start from. - // mask_ is a pointer to the mask bits at (0,0). - // pStride and mStride are the strides of the pixel and mask buffers. - void maskRect(const Rect& r, const void* pixels, const void* mask_, - const Point& maskPos=Point(0, 0), - int pStride=0, int mStride=0); - - // pixel is the Pixel value to be used where mask_ is set - void maskRect(const Rect& r, Pixel pixel, const void* mask_, - const Point& maskPos=Point(0, 0), - int mStride=0); - // Render in a specific format // Does the exact same thing as the above methods, but the given // pixel values are defined by the given PixelFormat. diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index 5040b658..51e1105a 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -1,6 +1,6 @@ /* 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 + * Copyright 2009-2017 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 @@ -301,38 +301,36 @@ void SMsgWriter::endMsg() void SMsgWriter::writePseudoRects() { if (needSetCursor) { - rdr::U8* data; - const Cursor& cursor = cp->cursor(); - data = new rdr::U8[cursor.area() * cp->pf().bpp/8]; - cursor.getImage(cp->pf(), data, cursor.getRect()); + rdr::U8Array data(cursor.width()*cursor.height() * cp->pf().bpp/8); + rdr::U8Array mask(cursor.getMask()); + + const rdr::U8* in; + rdr::U8* out; + + in = cursor.getBuffer(); + out = data.buf; + for (int i = 0;i < cursor.width()*cursor.height();i++) { + cp->pf().bufferFromRGB(out, in, 1); + in += 4; + out += cp->pf().bpp/8; + } writeSetCursorRect(cursor.width(), cursor.height(), - cursor.hotspot.x, cursor.hotspot.y, - data, cursor.mask.buf); + cursor.hotspot().x, cursor.hotspot().y, + data.buf, mask.buf); needSetCursor = false; - - delete [] data; } if (needSetXCursor) { const Cursor& cursor = cp->cursor(); - Pixel pix0, pix1; - rdr::U8 rgb0[3], rgb1[3]; - rdr::U8Array bitmap(cursor.getBitmap(&pix0, &pix1)); - - if (!bitmap.buf) { - // FIXME: We could reduce to two colors. - throw Exception("SMsgWriter::writePseudoRects: Unable to send multicolor cursor: RichCursor not supported by client"); - } - - cp->pf().rgbFromPixel(pix0, &rgb0[0], &rgb0[1], &rgb0[2]); - cp->pf().rgbFromPixel(pix1, &rgb1[0], &rgb1[1], &rgb1[2]); + rdr::U8Array bitmap(cursor.getBitmap()); + rdr::U8Array mask(cursor.getMask()); writeSetXCursorRect(cursor.width(), cursor.height(), - cursor.hotspot.x, cursor.hotspot.y, - rgb0, rgb1, bitmap.buf, cursor.mask.buf); + cursor.hotspot().x, cursor.hotspot().y, + bitmap.buf, mask.buf); needSetXCursor = false; } @@ -452,8 +450,6 @@ void SMsgWriter::writeSetCursorRect(int width, int height, void SMsgWriter::writeSetXCursorRect(int width, int height, int hotspotX, int hotspotY, - const rdr::U8 pix0[], - const rdr::U8 pix1[], const void* data, const void* mask) { if (!cp->supportsLocalXCursor) @@ -467,12 +463,12 @@ void SMsgWriter::writeSetXCursorRect(int width, int height, os->writeU16(height); os->writeU32(pseudoEncodingXCursor); if (width * height) { - os->writeU8(pix0[0]); - os->writeU8(pix0[1]); - os->writeU8(pix0[2]); - os->writeU8(pix1[0]); - os->writeU8(pix1[1]); - os->writeU8(pix1[2]); + os->writeU8(255); + os->writeU8(255); + os->writeU8(255); + os->writeU8(0); + os->writeU8(0); + os->writeU8(0); os->writeBytes(data, (width+7)/8 * height); os->writeBytes(mask, (width+7)/8 * height); } diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index 917b933c..a516e103 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -126,7 +126,6 @@ namespace rfb { const void* data, const void* mask); void writeSetXCursorRect(int width, int height, int hotspotX, int hotspotY, - const rdr::U8 pix0[], const rdr::U8 pix1[], const void* data, const void* mask); ConnParams* cp; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 74c40d15..e7a5dcf1 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -1119,7 +1119,7 @@ void VNCSConnectionST::setCursor() if (state() != RFBSTATE_NORMAL) return; - cp.setCursor(server->cursor); + cp.setCursor(*server->cursor); if (!writer()->writeSetCursor()) { if (!writer()->writeSetXCursor()) { diff --git a/common/rfb/VNCServer.h b/common/rfb/VNCServer.h index c76e5c9c..982a4ff5 100644 --- a/common/rfb/VNCServer.h +++ b/common/rfb/VNCServer.h @@ -64,16 +64,10 @@ namespace rfb { virtual void closeClients(const char* reason) = 0; // setCursor() tells the server that the cursor has changed. The - // cursorData argument contains width*height pixel values in the pixel - // buffer's format. The mask argument is a bitmask with a 1-bit meaning - // the corresponding pixel in cursorData is valid. The mask consists of - // left-to-right, top-to-bottom scanlines, where each scanline is padded to - // a whole number of bytes [(width+7)/8]. Within each byte the most - // significant bit represents the leftmost pixel, and the bytes are simply - // in left-to-right order. The server takes its own copy of the data in - // cursorData and mask. + // cursorData argument contains width*height rgba quadruplets with + // non-premultiplied alpha. virtual void setCursor(int width, int height, const Point& hotspot, - const void* cursorData, const void* mask) = 0; + const rdr::U8* cursorData) = 0; // setCursorPos() tells the server the current position of the cursor. virtual void setCursorPos(const Point& p) = 0; diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index e15cd701..81eed37a 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-2017 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 @@ -82,6 +82,7 @@ VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), blockCounter(0), pb(0), name(strDup(name_)), pointerClient(0), comparer(0), + cursor(new Cursor(0, 0, Point(), NULL)), renderedCursorInvalid(false), queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance), lastConnectionTime(0), disableclients(false), @@ -113,6 +114,8 @@ VNCServerST::~VNCServerST() if (comparer) comparer->logStats(); delete comparer; + + delete cursor; } @@ -314,7 +317,6 @@ void VNCServerST::setPixelBuffer(PixelBuffer* pb_, const ScreenSet& layout) } comparer = new ComparingUpdateTracker(pb); - cursor.setPF(pb->getPF()); renderedCursorInvalid = true; // Make sure that we have at least one screen @@ -430,14 +432,11 @@ void VNCServerST::add_copied(const Region& dest, const Point& delta) } void VNCServerST::setCursor(int width, int height, const Point& newHotspot, - const void* data, const void* mask) + const rdr::U8* data) { - cursor.hotspot = newHotspot; - cursor.setSize(width, height); - cursor.imageRect(cursor.getRect(), data); - memcpy(cursor.mask.buf, mask, cursor.maskLen()); - - cursor.crop(); + delete cursor; + cursor = new Cursor(width, height, newHotspot, data); + cursor->crop(); renderedCursorInvalid = true; @@ -618,8 +617,9 @@ bool VNCServerST::checkUpdate() Region toCheck = ui.changed.union_(ui.copied); if (renderCursor) { - Rect clippedCursorRect - = cursor.getRect(cursorPos.subtract(cursor.hotspot)).intersect(pb->getRect()); + Rect clippedCursorRect = Rect(0, 0, cursor->width(), cursor->height()) + .translate(cursorPos.subtract(cursor->hotspot())) + .intersect(pb->getRect()); if (!renderedCursorInvalid && (toCheck.intersect(clippedCursorRect) .is_empty())) { @@ -640,7 +640,7 @@ bool VNCServerST::checkUpdate() comparer->getUpdateInfo(&ui, pb->getRect()); if (renderCursor) { - renderedCursor.update(pb, &cursor, cursorPos); + renderedCursor.update(pb, cursor, cursorPos); renderedCursorInvalid = false; } diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index 0ced12a4..49757164 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -98,7 +98,7 @@ namespace rfb { virtual void add_changed(const Region ®ion); virtual void add_copied(const Region &dest, const Point &delta); virtual void setCursor(int width, int height, const Point& hotspot, - const void* cursorData, const void* mask); + const rdr::U8* data); virtual void setCursorPos(const Point& p); virtual void bell(); @@ -218,7 +218,7 @@ namespace rfb { ComparingUpdateTracker* comparer; Point cursorPos; - Cursor cursor; + Cursor* cursor; RenderedCursor renderedCursor; bool renderedCursorInvalid; diff --git a/tests/decperf.cxx b/tests/decperf.cxx index 1fd763e5..3b929a4b 100644 --- a/tests/decperf.cxx +++ b/tests/decperf.cxx @@ -49,7 +49,7 @@ public: virtual void setDesktopSize(int w, int h); virtual void setPixelFormat(const rfb::PixelFormat& pf); - virtual void setCursor(int, int, const rfb::Point&, void*, void*); + virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*); virtual void framebufferUpdateStart(); virtual void framebufferUpdateEnd(); virtual void setColourMapEntries(int, int, rdr::U16*); @@ -94,7 +94,7 @@ void CConn::setPixelFormat(const rfb::PixelFormat& pf) CConnection::setPixelFormat(filePF); } -void CConn::setCursor(int, int, const rfb::Point&, void*, void*) +void CConn::setCursor(int, int, const rfb::Point&, const rdr::U8*) { } diff --git a/tests/encperf.cxx b/tests/encperf.cxx index d58d82ea..7b9ff816 100644 --- a/tests/encperf.cxx +++ b/tests/encperf.cxx @@ -90,7 +90,7 @@ public: unsigned long long& rawEquivalent); virtual void setDesktopSize(int w, int h); - virtual void setCursor(int, int, const rfb::Point&, void*, void*); + virtual void setCursor(int, int, const rfb::Point&, const rdr::U8*); virtual void framebufferUpdateStart(); virtual void framebufferUpdateEnd(); virtual void dataRect(const rfb::Rect&, int); @@ -207,7 +207,7 @@ void CConn::setDesktopSize(int w, int h) setFramebuffer(pb); } -void CConn::setCursor(int, int, const rfb::Point&, void*, void*) +void CConn::setCursor(int, int, const rfb::Point&, const rdr::U8*) { } diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc index 1428791e..4eb0dff1 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.cc +++ b/unix/xserver/hw/vnc/XserverDesktop.cc @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2015 Pierre Ossman for Cendio AB + * Copyright 2009-2017 Pierre Ossman for Cendio AB * Copyright 2014 Brian P. Hinz * * This is free software; you can redistribute it and/or modify @@ -344,46 +344,37 @@ void XserverDesktop::setCursor(int width, int height, int hotX, int hotY, const unsigned char *rgbaData) { rdr::U8* cursorData; - rdr::U8* cursorMask; - int rfbMaskBytesPerRow; rdr::U8 *out; const unsigned char *in; - rdr::U8 rgb[3]; - cursorData = new rdr::U8[width * height * (getPF().bpp / 8)]; - - rfbMaskBytesPerRow = (width + 7) / 8; - - cursorMask = new rdr::U8[rfbMaskBytesPerRow * height]; - - memset(cursorMask, 0, rfbMaskBytesPerRow * height); + cursorData = new rdr::U8[width * height * 4]; + // Un-premultiply alpha in = rgbaData; out = cursorData; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - rgb[0] = *in++; - rgb[1] = *in++; - rgb[2] = *in++; - - getPF().bufferFromRGB(out, rgb, 1); + rdr::U8 alpha; - if (*in++ > 127) - cursorMask[y * rfbMaskBytesPerRow + x/8] |= 0x80>>(x%8); + alpha = in[3]; + if (alpha == 0) + alpha = 1; // Avoid division by zero - out += getPF().bpp/8; + *out++ = (unsigned)*in++ * 255/alpha; + *out++ = (unsigned)*in++ * 255/alpha; + *out++ = (unsigned)*in++ * 255/alpha; + *out++ = *in++; } } try { - server->setCursor(width, height, Point(hotX, hotY), cursorData, cursorMask); + server->setCursor(width, height, Point(hotX, hotY), cursorData); } catch (rdr::Exception& e) { vlog.error("XserverDesktop::setCursor: %s",e.str()); } delete [] cursorData; - delete [] cursorMask; } void XserverDesktop::add_changed(const rfb::Region ®ion) diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx index cc356263..a692732c 100644 --- a/vncviewer/CConn.cxx +++ b/vncviewer/CConn.cxx @@ -444,9 +444,9 @@ void CConn::dataRect(const Rect& r, int encoding) } void CConn::setCursor(int width, int height, const Point& hotspot, - void* data, void* mask) + const rdr::U8* data) { - desktop->setCursor(width, height, hotspot, data, mask); + desktop->setCursor(width, height, hotspot, data); } void CConn::fence(rdr::U32 flags, unsigned len, const char data[]) diff --git a/vncviewer/CConn.h b/vncviewer/CConn.h index c934f3da..d6dd4a75 100644 --- a/vncviewer/CConn.h +++ b/vncviewer/CConn.h @@ -66,7 +66,7 @@ public: void dataRect(const rfb::Rect& r, int encoding); void setCursor(int width, int height, const rfb::Point& hotspot, - void* data, void* mask); + const rdr::U8* data); void fence(rdr::U32 flags, unsigned len, const char data[]); diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 190b39cc..acb0a729 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -266,9 +266,9 @@ void DesktopWindow::resizeFramebuffer(int new_w, int new_h) void DesktopWindow::setCursor(int width, int height, const rfb::Point& hotspot, - void* data, void* mask) + const rdr::U8* data) { - viewport->setCursor(width, height, hotspot, data, mask); + viewport->setCursor(width, height, hotspot, data); } diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 3dfaaea8..11f3dc20 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -62,7 +62,7 @@ public: // New image for the locally rendered cursor void setCursor(int width, int height, const rfb::Point& hotspot, - void* data, void* mask); + const rdr::U8* data); // Fl_Window callback methods void draw(); diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index b5c516fa..6a23526a 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -180,21 +180,20 @@ static const char * dotcursor_xpm[] = { " "}; void Viewport::setCursor(int width, int height, const Point& hotspot, - void* data, void* mask) + const rdr::U8* data) { + int i; + if (cursor) { if (!cursor->alloc_array) delete [] cursor->array; delete cursor; } - int mask_len = ((width+7)/8) * height; - int i; - - for (i = 0; i < mask_len; i++) - if (((rdr::U8*)mask)[i]) break; + for (i = 0; i < width*height; i++) + if (data[i*4 + 3] != 0) break; - if ((i == mask_len) && dotWhenNoCursor) { + if ((i == width*height) && dotWhenNoCursor) { vlog.debug("cursor is empty - using dot"); Fl_Pixmap pxm(dotcursor_xpm); @@ -207,34 +206,9 @@ void Viewport::setCursor(int width, int height, const Point& hotspot, cursor = new Fl_RGB_Image(buffer, 1, 1, 4); cursorHotspot.x = cursorHotspot.y = 0; } else { - U8 *buffer = new U8[width*height*4]; - U8 *i, *o, *m; - int m_width; - - const PixelFormat *pf; - - pf = &cc->cp.pf(); - - i = (U8*)data; - o = buffer; - m = (U8*)mask; - m_width = (width+7)/8; - for (int y = 0;y < height;y++) { - for (int x = 0;x < width;x++) { - pf->rgbFromBuffer(o, i, 1); - - if (m[(m_width*y)+(x/8)] & 0x80>>(x%8)) - o[3] = 255; - else - o[3] = 0; - - o += 4; - i += pf->bpp/8; - } - } - + U8 *buffer = new U8[width * height * 4]; + memcpy(buffer, data, width * height * 4); cursor = new Fl_RGB_Image(buffer, width, height, 4); - cursorHotspot = hotspot; } } diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 0967fcbb..6f0710d3 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -45,7 +45,7 @@ public: // New image for the locally rendered cursor void setCursor(int width, int height, const rfb::Point& hotspot, - void* data, void* mask); + const rdr::U8* data); void draw(Surface* dst); diff --git a/win/rfb_win32/DeviceFrameBuffer.cxx b/win/rfb_win32/DeviceFrameBuffer.cxx index 22841f7e..3138cce2 100644 --- a/win/rfb_win32/DeviceFrameBuffer.cxx +++ b/win/rfb_win32/DeviceFrameBuffer.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2014 Pierre Ossman for Cendio AB + * Copyright 2014-2017 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 @@ -42,7 +42,7 @@ BoolParameter DeviceFrameBuffer::useCaptureBlt("UseCaptureBlt", // -=- DeviceFrameBuffer class DeviceFrameBuffer::DeviceFrameBuffer(HDC deviceContext, const Rect& wRect) - : DIBSectionBuffer(deviceContext), device(deviceContext), cursorBm(deviceContext), + : DIBSectionBuffer(deviceContext), device(deviceContext), ignoreGrabErrors(false) { @@ -76,9 +76,6 @@ DeviceFrameBuffer::DeviceFrameBuffer(HDC deviceContext, const Rect& wRect) // Configure the underlying DIB to match the device DIBSectionBuffer::setPF(DeviceContext::getPF(device)); DIBSectionBuffer::setSize(w, h); - - // Configure the cursor buffer - cursorBm.setPF(format); } DeviceFrameBuffer::~DeviceFrameBuffer() { @@ -134,15 +131,14 @@ void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server) // - If hCursor is null then there is no cursor - clear the old one if (hCursor == 0) { - server->setCursor(0, 0, Point(), 0, 0); + server->setCursor(0, 0, Point(), NULL); return; } try { - const rdr::U8* buffer; - rdr::U8* rwbuffer; - int stride; + int width, height; + rdr::U8Array buffer; // - Get the size and other details about the cursor. @@ -156,97 +152,170 @@ void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server) if (maskInfo.bmBitsPixel != 1) throw rdr::Exception("unsupported cursor mask format"); - // - Create the cursor pixel buffer and mask storage - // NB: The cursor pixel buffer is NOT used here. Instead, we - // pass the cursorBm.data pointer directly, to save overhead. + width = maskInfo.bmWidth; + height = maskInfo.bmHeight; + if (!iconInfo.hbmColor) + height /= 2; - cursor.setSize(maskInfo.bmWidth, maskInfo.bmHeight); - cursor.setPF(format); - cursor.hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot); + buffer.buf = new rdr::U8[width * height * 4]; - // - Get the AND and XOR masks. There is only an XOR mask if this is not a - // colour cursor. + Point hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot); - if (!iconInfo.hbmColor) - cursor.setSize(cursor.width(), cursor.height() / 2); - rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight); - rdr::U8* xorMask = mask.buf + cursor.height() * maskInfo.bmWidthBytes; - - if (!GetBitmapBits(iconInfo.hbmMask, - maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf)) - throw rdr::SystemException("GetBitmapBits failed", GetLastError()); - - // Configure the cursor bitmap - cursorBm.setSize(cursor.width(), cursor.height()); - - // Draw the cursor into the bitmap - BitmapDC dc(device, cursorBm.bitmap); - if (!DrawIconEx(dc, 0, 0, hCursor, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT)) - throw rdr::SystemException("unable to render cursor", GetLastError()); - - // Replace any XORed pixels with xorColour, because RFB doesn't support - // XORing of cursors. XORing is used for the I-beam cursor, which is most - // often used over a white background, but also sometimes over a black - // background. We set the XOR'd pixels to black, then draw a white outline - // around the whole cursor. - - // *** should we replace any pixels not set in mask to zero, to ensure - // that irrelevant data doesn't screw compression? - - bool doOutline = false; - if (!iconInfo.hbmColor) { - rwbuffer = cursorBm.getBufferRW(cursorBm.getRect(), &stride); - Pixel xorColour = format.pixelFromRGB((rdr::U16)0, (rdr::U16)0, (rdr::U16)0); - for (int y = 0; y < cursor.height(); y++) { - for (int x = 0; x < cursor.width(); x++) { + if (iconInfo.hbmColor) { + // Colour cursor + + BITMAPV5HEADER bi; + BitmapDC dc(device, iconInfo.hbmColor); + + memset(&bi, 0, sizeof(BITMAPV5HEADER)); + + bi.bV5Size = sizeof(BITMAPV5HEADER); + bi.bV5Width = width; + bi.bV5Height = -height; // Negative for top-down + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x000000FF; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x00FF0000; + bi.bV5AlphaMask = 0xFF000000; + + if (!GetDIBits(dc, iconInfo.hbmColor, 0, height, + buffer.buf, (LPBITMAPINFO)&bi, DIB_RGB_COLORS)) + throw rdr::SystemException("GetDIBits", GetLastError()); + + // We may not get the RGBA order we want, so shuffle things around + int ridx, gidx, bidx, aidx; + + ridx = __builtin_ffs(bi.bV5RedMask) / 8; + gidx = __builtin_ffs(bi.bV5GreenMask) / 8; + bidx = __builtin_ffs(bi.bV5BlueMask) / 8; + // Usually not set properly + aidx = 6 - ridx - gidx - bidx; + + if ((bi.bV5RedMask != ((unsigned)0xff << ridx*8)) || + (bi.bV5GreenMask != ((unsigned)0xff << gidx*8)) || + (bi.bV5BlueMask != ((unsigned)0xff << bidx*8))) + throw rdr::Exception("unsupported cursor colour format"); + + rdr::U8* rwbuffer = buffer.buf; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + rdr::U8 r, g, b, a; + + r = rwbuffer[ridx]; + g = rwbuffer[gidx]; + b = rwbuffer[bidx]; + a = rwbuffer[aidx]; + + rwbuffer[0] = r; + rwbuffer[1] = g; + rwbuffer[2] = b; + rwbuffer[3] = a; + + rwbuffer += 4; + } + } + } else { + // B/W cursor + + rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight); + rdr::U8* andMask = mask.buf; + rdr::U8* xorMask = mask.buf + height * maskInfo.bmWidthBytes; + + if (!GetBitmapBits(iconInfo.hbmMask, + maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf)) + throw rdr::SystemException("GetBitmapBits", GetLastError()); + + bool doOutline = false; + rdr::U8* rwbuffer = buffer.buf; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { int byte = y * maskInfo.bmWidthBytes + x / 8; int bit = 7 - x % 8; - if ((mask.buf[byte] & (1 << bit)) && (xorMask[byte] & (1 << bit))) - { - mask.buf[byte] &= ~(1 << bit); - - switch (format.bpp) { - case 8: - rwbuffer[y * cursor.width() + x] = xorColour; break; - case 16: - rwbuffer[y * cursor.width() + x] = xorColour; break; - case 32: - rwbuffer[y * cursor.width() + x] = xorColour; break; - } + + if (!(andMask[byte] & (1 << bit))) { + // Valid pixel, so make it opaque + rwbuffer[3] = 0xff; + + // Black or white? + if (xorMask[byte] & (1 << bit)) + rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0xff; + else + rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0; + } else if (xorMask[byte] & (1 << bit)) { + // Replace any XORed pixels with black, because RFB doesn't support + // XORing of cursors. XORing is used for the I-beam cursor, which is most + // often used over a white background, but also sometimes over a black + // background. We set the XOR'd pixels to black, then draw a white outline + // around the whole cursor. + + rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0; + rwbuffer[3] = 0xff; doOutline = true; + } else { + // Transparent pixel + rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = rwbuffer[3] = 0; } + + rwbuffer += 4; } } - cursorBm.commitBufferRW(cursorBm.getRect()); - } - - // Finally invert the AND mask so it's suitable for RFB and pack it into - // the minimum number of bytes per row. - - int maskBytesPerRow = (cursor.width() + 7) / 8; - for (int j = 0; j < cursor.height(); j++) { - for (int i = 0; i < maskBytesPerRow; i++) - cursor.mask.buf[j * maskBytesPerRow + i] - = ~mask.buf[j * maskInfo.bmWidthBytes + i]; - } - - if (doOutline) { - vlog.debug("drawing cursor outline!"); + if (doOutline) { + vlog.debug("drawing cursor outline!"); + + // The buffer needs to be slightly larger to make sure there + // is room for the outline pixels + rdr::U8Array outline((width + 2)*(height + 2)*4); + memset(outline.buf, 0, (width + 2)*(height + 2)*4); + + // Pass 1, outline everything + rdr::U8* in = buffer.buf; + rdr::U8* out = outline.buf + width*4 + 4; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // Visible pixel? + if (in[3] > 0) { + // Outline above... + memset(out - (width+2)*4 - 4, 0xff, 4 * 3); + // ...besides... + memset(out - 4, 0xff, 4 * 3); + // ...and above + memset(out + (width+2)*4 - 4, 0xff, 4 * 3); + } + in += 4; + out += 4; + } + // outline is slightly larger + out += 2*4; + } - buffer = cursorBm.getBuffer(cursorBm.getRect(), &stride); - cursor.imageRect(cursorBm.getRect(), buffer, stride); + // Pass 2, overwrite with actual cursor + in = buffer.buf; + out = outline.buf + width*4 + 4; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (in[3] > 0) + memcpy(out, in, 4); + in += 4; + out += 4; + } + out += 2*4; + } - cursor.drawOutline(format.pixelFromRGB((rdr::U16)0xffff, (rdr::U16)0xffff, (rdr::U16)0xffff)); + width += 2; + height += 2; + hotspot.x += 1; + hotspot.y += 1; - buffer = cursor.getBuffer(cursor.getRect(), &stride); - cursorBm.imageRect(cursor.getRect(), buffer, stride); + delete [] buffer.buf; + buffer.buf = outline.takeBuf(); + } } - buffer = cursorBm.getBuffer(cursorBm.getRect(), &stride); - server->setCursor(cursor.width(), cursor.height(), cursor.hotspot, - buffer, cursor.mask.buf); + server->setCursor(width, height, hotspot, buffer.buf); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); diff --git a/win/rfb_win32/DeviceFrameBuffer.h b/win/rfb_win32/DeviceFrameBuffer.h index 8e280f8a..6fccf9fa 100644 --- a/win/rfb_win32/DeviceFrameBuffer.h +++ b/win/rfb_win32/DeviceFrameBuffer.h @@ -92,8 +92,6 @@ namespace rfb { Point desktopToDevice(const Point p) const {return p.translate(deviceCoords.tl);} HDC device; - DIBSectionBuffer cursorBm; - Cursor cursor; Rect deviceCoords; bool ignoreGrabErrors; }; |