diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js index 51da9548c..e39dc4b5c 100644 --- a/browser/src/layer/tile/CanvasTileLayer.js +++ b/browser/src/layer/tile/CanvasTileLayer.js @@ -6742,36 +6742,24 @@ L.CanvasTileLayer = L.Layer.extend({ return ctx; }, - _brgatorgba: function(rawDelta) { + _unpremultiply: function(rawDelta) { var len = rawDelta.byteLength / 4; var delta32 = new Uint32Array(rawDelta.buffer, rawDelta.byteOffset, len); var resultu32 = new Uint32Array(len); var resultu8 = new Uint8ClampedArray(resultu32.buffer, resultu32.byteOffset, resultu32.byteLength); for (var i32 = 0; i32 < len; ++i32) { - // premultiplied brga -> unpremultiplied rgba - // If the previous input pixel was the same as the current input pixel - // just copy the previous output pixel as the current output pixel - if (i32 > 0 && delta32[i32] === delta32[i32 - 1]) - resultu32[i32] = resultu32[i32 - 1]; - else - { + // premultiplied rgba -> unpremultiplied rgba + var alpha = delta32[i32] >>> 24; + if (alpha === 255) { + resultu32[i32] = delta32[i32]; + } + else if (alpha !== 0) { // dest can remain at ctored 0 if alpha is 0 var i8 = i32 * 4; - var alpha = rawDelta[i8 + 3]; - if (alpha === 255) { - resultu8[i8] = rawDelta[i8 + 2]; - resultu8[i8 + 1] = rawDelta[i8 + 1]; - resultu8[i8 + 2] = rawDelta[i8]; - resultu8[i8 + 3] = 255; - } - else if (alpha === 0) - resultu32[i32] = 0; - else // forced to do the math - { - resultu8[i8] = Math.ceil(rawDelta[i8 + 2] * 255 / alpha); - resultu8[i8 + 1] = Math.ceil(rawDelta[i8 + 1] * 255 / alpha); - resultu8[i8 + 2] = Math.ceil(rawDelta[i8] * 255 / alpha); - resultu8[i8 + 3] = alpha; - } + // forced to do the math + resultu8[i8] = Math.ceil(rawDelta[i8] * 255 / alpha); + resultu8[i8 + 1] = Math.ceil(rawDelta[i8 + 1] * 255 / alpha); + resultu8[i8 + 2] = Math.ceil(rawDelta[i8 + 2] * 255 / alpha); + resultu8[i8 + 3] = alpha; } } return resultu8; @@ -6853,7 +6841,7 @@ L.CanvasTileLayer = L.Layer.extend({ { // FIXME: use zstd to de-compress directly into a Uint8ClampedArray len = canvas.width * canvas.height * 4; - var pixelArray = this._brgatorgba(delta.subarray(0, len)); + var pixelArray = this._unpremultiply(delta.subarray(0, len)); imgData = new ImageData(pixelArray, canvas.width, canvas.height); if (this._debugDeltas) @@ -6953,7 +6941,7 @@ L.CanvasTileLayer = L.Layer.extend({ span *= 4; // copy so this is suitably aligned for a Uint32Array view var tmpu8 = new Uint8Array(delta.subarray(i, i + span)); - var pixelData = this._brgatorgba(tmpu8); + var pixelData = this._unpremultiply(tmpu8); // imgData.data[offset + 1] = 256; // debug - greener start for (var j = 0; j < span; ++j) imgData.data[offset++] = pixelData[j]; diff --git a/common/Png.hpp b/common/Png.hpp index 69e0cfc69..cf277ae86 100644 --- a/common/Png.hpp +++ b/common/Png.hpp @@ -80,9 +80,9 @@ extern "C" } -/* Unpremultiplies data and converts native endian ARGB => RGBA bytes */ +/* Unpremultiplies data and converts native endian BGRA => RGBA bytes */ static void -unpremultiply_data (png_structp /*png*/, png_row_infop row_info, png_bytep data) +unpremultiply_bgra_data (png_structp /*png*/, png_row_infop row_info, png_bytep data) { unsigned int i; @@ -116,6 +116,40 @@ unpremultiply_data (png_structp /*png*/, png_row_infop row_info, png_bytep data) } } +/* Unpremultiplies data already in RGBA order*/ +static void +unpremultiply_rgba_data (png_structp /*png*/, png_row_infop row_info, png_bytep data) +{ + unsigned int i; + + for (i = 0; i < row_info->rowbytes; i += 4) + { + uint8_t *b = &data[i]; + uint32_t pix; + uint8_t alpha; + + std::memcpy (&pix, b, sizeof (uint32_t)); + + alpha = (pix & 0xff000000) >> 24; + if (alpha == 255) + { + std::memcpy(b, &pix, sizeof (uint32_t)); + } + else if (alpha == 0) + { + b[0] = b[1] = b[2] = b[3] = 0; + } + else + { + b[0] = (((pix & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; + b[1] = (((pix & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; + b[2] = (((pix & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; + b[3] = alpha; + } + } +} + + /// This function uses setjmp which may clobbers non-trivial objects. /// So we can't use logging or create complex C++ objects in this frame. /// Specifically, logging uses std::string objects, and GCC gives the following: @@ -161,9 +195,14 @@ inline bool impl_encodeSubBufferToPNG(unsigned char* pixmap, size_t startX, size png_write_info(png_ptr, info_ptr); - if (mode == LOK_TILEMODE_BGRA) + switch (mode) { - png_set_write_user_transform_fn (png_ptr, unpremultiply_data); + case LOK_TILEMODE_BGRA: + png_set_write_user_transform_fn (png_ptr, unpremultiply_bgra_data); + break; + case LOK_TILEMODE_RGBA: + png_set_write_user_transform_fn (png_ptr, unpremultiply_rgba_data); + break; } for (int y = 0; y < height; ++y) diff --git a/kit/Delta.hpp b/kit/Delta.hpp index 53dd2f0fd..07726c333 100644 --- a/kit/Delta.hpp +++ b/kit/Delta.hpp @@ -146,7 +146,8 @@ class DeltaGenerator { // Create a diff from our state to new state in curRow void diffRowTo(const DeltaBitmapRow &curRow, const int width, const int curY, - std::vector &output) const + std::vector &output, + LibreOfficeKitTileMode mode) const { PixIterator oldPixels(*this); PixIterator curPixels(curRow); @@ -188,7 +189,7 @@ class DeltaGenerator { copy_row(reinterpret_cast(&output[dest]), (const unsigned char *)(scratch), - diff); + diff, mode); LOG_TRC("row " << curY << " different " << diff << "pixels"); x += diff; @@ -352,15 +353,26 @@ class DeltaGenerator { } static void - copy_row (unsigned char *dest, const unsigned char *srcBytes, unsigned int count) + copy_row (unsigned char *dest, const unsigned char *srcBytes, unsigned int count, LibreOfficeKitTileMode mode) { - std::memcpy(dest, srcBytes, count * 4); + switch (mode) + { + case LOK_TILEMODE_RGBA: + std::memcpy(dest, srcBytes, count * 4); + break; + case LOK_TILEMODE_BGRA: + std::memcpy(dest, srcBytes, count * 4); + for (size_t j = 0; j < count * 4; j += 4) + std::swap(dest[j], dest[j+2]); + break; + } } bool makeDelta( const DeltaData &prev, const DeltaData &cur, - std::vector& outStream) + std::vector& outStream, + LibreOfficeKitTileMode mode) { // TODO: should we split and compress alpha separately ? if (prev.getWidth() != cur.getWidth() || prev.getHeight() != cur.getHeight()) @@ -430,7 +442,7 @@ class DeltaGenerator { continue; // Our row is just that different: - prev.getRow(y).diffRowTo(cur.getRow(y), prev.getWidth(), y, output); + prev.getRow(y).diffRowTo(cur.getRow(y), prev.getWidth(), y, output, mode); } LOG_TRC("Created delta of size " << output.size()); if (output.empty()) @@ -520,7 +532,8 @@ class DeltaGenerator { int bufferWidth, int bufferHeight, const TileLocation &loc, std::vector& output, - TileWireId wid, bool forceKeyframe) + TileWireId wid, bool forceKeyframe, + LibreOfficeKitTileMode mode) { if ((width & 0x1) != 0) // power of two - RGBA { @@ -557,7 +570,7 @@ class DeltaGenerator { bool delta = false; if (!forceKeyframe) - delta = makeDelta(*cacheEntry, *update, output); + delta = makeDelta(*cacheEntry, *update, output, mode); // no two threads can be working on the same DeltaData. cacheEntry->replaceAndFree(update); @@ -629,7 +642,7 @@ class DeltaGenerator { if (!createDelta(pixmap, startX, startY, width, height, bufferWidth, bufferHeight, - loc, output, wid, forceKeyframe)) + loc, output, wid, forceKeyframe, mode)) { // FIXME: should stream it in =) size_t maxCompressed = ZSTD_COMPRESSBOUND((size_t)width * height * 4); @@ -655,7 +668,7 @@ class DeltaGenerator { // FIXME: should we RLE in pixels first ? for (int y = 0; y < height; ++y) { - copy_row(fixedupLine, pixmap + ((startY + y) * bufferWidth * 4) + (startX * 4), width); + copy_row(fixedupLine, pixmap + ((startY + y) * bufferWidth * 4) + (startX * 4), width, mode); ZSTD_inBuffer inb; inb.src = fixedupLine; diff --git a/kit/DummyLibreOfficeKit.cpp b/kit/DummyLibreOfficeKit.cpp index 002131e12..7c84b327f 100644 --- a/kit/DummyLibreOfficeKit.cpp +++ b/kit/DummyLibreOfficeKit.cpp @@ -357,7 +357,7 @@ static void doc_paintPartTile(LibreOfficeKitDocument* pThis, static int doc_getTileMode(LibreOfficeKitDocument* /*pThis*/) { - return LOK_TILEMODE_BGRA; + return LOK_TILEMODE_RGBA; } static void doc_getDocumentSize(LibreOfficeKitDocument* pThis, diff --git a/test/DeltaTests.cpp b/test/DeltaTests.cpp index 7cad67693..3341690ea 100644 --- a/test/DeltaTests.cpp +++ b/test/DeltaTests.cpp @@ -399,14 +399,14 @@ void DeltaTests::testDeltaSequence() LOK_ASSERT(gen.createDelta( reinterpret_cast(&text[0]), 0, 0, width, height, width, height, - TileLocation(1, 2, 3, 0, 1), delta, textWid, false) == false); + TileLocation(1, 2, 3, 0, 1), delta, textWid, false, LOK_TILEMODE_RGBA) == false); LOK_ASSERT(delta.empty()); // Build a delta between text2 & textWid LOK_ASSERT(gen.createDelta( reinterpret_cast(&text2[0]), 0, 0, width, height, width, height, - TileLocation(1, 2, 3, 0, 1), delta, text2Wid, false) == true); + TileLocation(1, 2, 3, 0, 1), delta, text2Wid, false, LOK_TILEMODE_RGBA) == true); LOK_ASSERT(delta.size() > 0); checkzDelta(delta, "text2 to textWid"); @@ -419,7 +419,7 @@ void DeltaTests::testDeltaSequence() LOK_ASSERT(gen.createDelta( reinterpret_cast(&text[0]), 0, 0, width, height, width, height, - TileLocation(1, 2, 3, 0, 1), two2one, textWid, false) == true); + TileLocation(1, 2, 3, 0, 1), two2one, textWid, false, LOK_TILEMODE_RGBA) == true); LOK_ASSERT(two2one.size() > 0); checkzDelta(two2one, "text to text2Wid"); @@ -458,14 +458,14 @@ void DeltaTests::testDeltaCopyOutOfBounds() LOK_ASSERT(gen.createDelta( reinterpret_cast(&text[0]), 0, 0, width, height, width, height, - TileLocation(1, 2, 3, 0, 1), delta, textWid, false) == false); + TileLocation(1, 2, 3, 0, 1), delta, textWid, false, LOK_TILEMODE_RGBA) == false); LOK_ASSERT(delta.empty()); // Build a delta between the two frames LOK_ASSERT(gen.createDelta( reinterpret_cast(&text2[0]), 0, 0, width, height, width, height, - TileLocation(1, 2, 3, 0, 1), delta, text2Wid, false) == true); + TileLocation(1, 2, 3, 0, 1), delta, text2Wid, false, LOK_TILEMODE_RGBA) == true); LOK_ASSERT(delta.size() > 0); checkzDelta(delta, "copy out of bounds");