[Libreoffice-commits] online.git: common/RenderTiles.hpp ios/Mobile loleaflet/src

Tor Lillqvist (via logerrit) logerrit at kemper.freedesktop.org
Tue Jul 14 15:00:55 UTC 2020


 common/RenderTiles.hpp                |  189 ++++++++++++++++++++++++++++++++--
 ios/Mobile/DocumentViewController.mm  |    9 +
 loleaflet/src/core/Socket.js          |    2 
 loleaflet/src/layer/tile/TileLayer.js |    3 
 4 files changed, 196 insertions(+), 7 deletions(-)

New commits:
commit e64bbeb81914d5f58681cb4ea15fec4f5128a76b
Author:     Tor Lillqvist <tml at collabora.com>
AuthorDate: Mon Jul 13 23:00:44 2020 +0300
Commit:     Tor Lillqvist <tml at collabora.com>
CommitDate: Tue Jul 14 17:00:35 2020 +0200

    Pass rendered tiles as uncompressed BMP files in the iOS app
    
    Avoids the need for PNG encoding (takes significant amount of CPU
    time) and Base64 encoding in the app process, transfer to JavaScript
    (running in a WebKit process of its own), and corresponding decoding
    (in the WebKit process). Instead simply pass the URL of each tile file
    to the JavaScript. Remove each BMP file once it has been loaded.
    
    Change-Id: I6e7b9450691679c64813979976c59f1763ec104c
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98710
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Michael Meeks <michael.meeks at collabora.com>

diff --git a/common/RenderTiles.hpp b/common/RenderTiles.hpp
index 03308879b..1385665a7 100644
--- a/common/RenderTiles.hpp
+++ b/common/RenderTiles.hpp
@@ -16,6 +16,12 @@
 #include <unordered_map>
 #include <vector>
 
+#ifdef IOS
+#import <fcntl.h>
+#import <sys/mman.h>
+#import <unistd.h>
+#endif
+
 #include "Png.hpp"
 #include "Rectangle.hpp"
 #include "TileDesc.hpp"
@@ -356,6 +362,110 @@ namespace RenderTiles
         renderedTiles.back().setImgSize(imgSize);
     }
 
+#ifdef IOS
+
+#pragma pack(push)
+#pragma pack(2)
+    typedef struct
+    {
+        uint16_t bfType;
+        uint32_t bfSize;
+        uint16_t bfReserved1;
+        uint16_t bfReserved2;
+        uint32_t bfOffBits;
+    } BITMAPFILEHEADER;
+#pragma pack(pop)
+
+    typedef uint32_t FXPT2DOT30; // Fixed point 2.30, whatever that means
+
+    typedef struct
+    {
+        FXPT2DOT30 ciexyzX;
+        FXPT2DOT30 ciexyzY;
+        FXPT2DOT30 ciexyzZ;
+    } CIEXYZ;
+
+    typedef struct
+    {
+        CIEXYZ ciexyzRed;
+        CIEXYZ ciexyzGreen;
+        CIEXYZ ciexyzBlue;
+    } CIEXYZTRIPLE;
+
+    // We must use the BITMAPV5HEADER to get proper alpha handling.
+    typedef struct
+    {
+        uint32_t bV5Size;
+        int32_t bV5Width;
+        int32_t bV5Height;
+        uint16_t bV5Planes;
+        uint16_t bV5BitCount;
+        uint32_t bV5Compression;
+        uint32_t bV5SizeImage;
+        int32_t bV5XPelsPerMeter;
+        int32_t bV5YPelsPerMeter;
+        uint32_t bV5ClrUsed;
+        uint32_t bV5ClrImportant;
+        uint32_t bV5RedMask;
+        uint32_t bV5GreenMask;
+        uint32_t bV5BlueMask;
+        uint32_t bV5AlphaMask;
+        uint32_t bV5CSType;
+        CIEXYZTRIPLE bV5Endpoints;
+        uint32_t bV5GammaRed;
+        uint32_t bV5GammaGreen;
+        uint32_t bV5GammaBlue;
+        uint32_t bV5Intent;
+        uint32_t bV5ProfileData;
+        uint32_t bV5ProfileSize;
+        uint32_t bV5Reserved;
+    } BITMAPV5HEADER;
+
+#define BI_BITFIELDS 3
+
+#define LCS_sRGB 0x73524742
+#define LCS_GM_IMAGES 4
+
+    static size_t bmpFileSize(int width, int height)
+    {
+        return sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV5HEADER) + width * height * 4;
+    }
+
+    static void generateBmpHeader(char *buffer, int width, int height)
+    {
+        assert(width%4 == 0);
+
+        BITMAPFILEHEADER *bf = (BITMAPFILEHEADER*)buffer;
+        bf->bfType = ('B' | ('M' << 8));
+        bf->bfSize = bmpFileSize(width, height);
+        bf->bfReserved1 = 0;
+        bf->bfReserved2 = 0;
+        bf->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV5HEADER);
+
+        BITMAPV5HEADER *bV5 = (BITMAPV5HEADER*) (buffer + sizeof(BITMAPFILEHEADER));
+        bV5->bV5Size = sizeof(BITMAPV5HEADER);
+        bV5->bV5Width = width;
+        bV5->bV5Height = -height;
+        bV5->bV5Planes = 1;
+        bV5->bV5BitCount = 32;
+        bV5->bV5Compression = BI_BITFIELDS;
+        bV5->bV5SizeImage = 0;
+        bV5->bV5XPelsPerMeter = 1000; // Dummy
+        bV5->bV5YPelsPerMeter = 1000;
+        bV5->bV5ClrUsed = 0;
+        bV5->bV5ClrImportant = 0;
+        bV5->bV5RedMask = 0x00FF0000;
+        bV5->bV5GreenMask = 0x0000FF00;
+        bV5->bV5BlueMask = 0x000000FF;
+        bV5->bV5AlphaMask = 0xFF000000;
+        bV5->bV5CSType = LCS_sRGB;
+        // Leave out fields that are ignored with above settings.
+        bV5->bV5Intent = LCS_GM_IMAGES; // ???
+        bV5->bV5Reserved = 0;
+    }
+
+#endif
+
     bool doRender(std::shared_ptr<lok::Document> document,
                   TileCombined &tileCombined,
                   PngCache &pngCache,
@@ -392,10 +502,14 @@ namespace RenderTiles
             tileRecs.push_back(rectangle);
         }
 
+        assert(tiles.size() == tileRecs.size());
+
         const size_t tilesByX = renderArea.getWidth() / tileCombined.getTileWidth();
         const size_t tilesByY = renderArea.getHeight() / tileCombined.getTileHeight();
-        const size_t pixmapWidth = tilesByX * tileCombined.getWidth();
-        const size_t pixmapHeight = tilesByY * tileCombined.getHeight();
+        const int pixelWidth = tileCombined.getWidth();
+        const int pixelHeight = tileCombined.getHeight();
+        const size_t pixmapWidth = tilesByX * pixelWidth;
+        const size_t pixmapHeight = tilesByY * pixelHeight;
 
         if (pixmapWidth > 4096 || pixmapHeight > 4096)
             LOG_WRN("Unusual extremely large tile combine of size " << pixmapWidth << 'x' << pixmapHeight);
@@ -420,15 +534,78 @@ namespace RenderTiles
                 renderArea.getWidth() << ", " << renderArea.getHeight() << ") " <<
                 " rendered in " << totalTime << " ms (" << area / elapsed << " MP/s).");
 
+#ifdef IOS
+
+        for (int i = 0; i < tiles.size(); i++)
+        {
+            static int bmpFileCounter = 0;
+            const int bmpId = bmpFileCounter++;
+
+            const size_t positionX = (tileRecs[i].getLeft() - renderArea.getLeft()) / tileCombined.getTileWidth();
+            const size_t positionY = (tileRecs[i].getTop() - renderArea.getTop()) / tileCombined.getTileHeight();
+
+            const int offsetX = positionX * pixelWidth;
+            const int offsetY = positionY * pixelHeight;
+
+            NSString *mmapFileBaseName = [NSString stringWithFormat:@"%d.bmp", bmpId];
+            NSURL *mmapFileURL = [[NSFileManager.defaultManager temporaryDirectory] URLByAppendingPathComponent:mmapFileBaseName];
+
+            int fd = open([[mmapFileURL path] UTF8String], O_RDWR|O_CREAT, 0666);
+            if (fd == -1)
+            {
+                LOG_SYS("Could not create file " << [[mmapFileURL path] UTF8String]);
+                return false;
+            }
+
+            const size_t mmapFileSize = bmpFileSize(pixelWidth, pixelHeight);
+
+            if (lseek(fd, mmapFileSize-1, SEEK_SET) == -1)
+            {
+                LOG_SYS("Could not seek in file " << [[mmapFileURL path] UTF8String]);
+                return false;
+            }
+
+            if (write(fd, "", 1) == -1)
+            {
+                LOG_SYS("Could not write at end of " << [[mmapFileURL path] UTF8String]);
+                return false;
+            }
+
+            char *mmapMemory = (char *)mmap(NULL, mmapFileSize, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0);
+            if (mmapMemory == MAP_FAILED)
+            {
+                LOG_SYS("Could not map in file " << [[mmapFileURL path] UTF8String]);
+                close(fd);
+                return false;
+            }
+
+            close(fd);
+
+            generateBmpHeader(mmapMemory, pixelWidth, pixelHeight);
+
+            for (int y = 0; y < pixelHeight; y++)
+                memcpy(mmapMemory + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV5HEADER) + y * pixelWidth * 4,
+                       pixmap.data() + (offsetY + y) * pixmapWidth * 4 + offsetX * 4,
+                       pixelWidth * 4);
+
+            if (munmap(mmapMemory, mmapFileSize) == -1)
+            {
+                LOG_SYS("Could not unmap file " << [[mmapFileURL path] UTF8String]);
+                return false;
+            }
+
+            std::string tileMsg = tiles[i].serialize("tile:", ADD_DEBUG_RENDERID) + std::string([[mmapFileURL absoluteString] UTF8String]);
+            outputMessage(tileMsg.c_str(), tileMsg.length());
+        }
+
+#else
+
         const auto mode = static_cast<LibreOfficeKitTileMode>(document->getTileMode());
 
         std::vector<char> output;
         output.reserve(pixmapSize);
 
         // Compress the area as tiles
-        const int pixelWidth = tileCombined.getWidth();
-        const int pixelHeight = tileCombined.getHeight();
-
         std::vector<TileDesc> renderedTiles;
         std::vector<TileDesc> duplicateTiles;
         std::vector<TileBinaryHash> duplicateHashes;
@@ -590,7 +767,7 @@ namespace RenderTiles
                 outputOffset += i.getImgSize();
             }
         }
-
+#endif
         return true;
     }
 }
diff --git a/ios/Mobile/DocumentViewController.mm b/ios/Mobile/DocumentViewController.mm
index 3f8744fe4..2467bbfbb 100644
--- a/ios/Mobile/DocumentViewController.mm
+++ b/ios/Mobile/DocumentViewController.mm
@@ -490,6 +490,15 @@ static IMP standardImpOfInputAccessoryView = nil;
                                  completion:nil];
                 return;
             }
+        } else if ([message.body hasPrefix:@"REMOVE "]) {
+            // Sent from the img element's onload event handler. Remove tile file once it has been loaded.
+            NSArray<NSString*> *messageBodyItems = [message.body componentsSeparatedByString:@" "];
+            assert([messageBodyItems count] == 2);
+            NSURL *tile = [NSURL URLWithString:messageBodyItems[1]];
+            if (unlink([[tile path] UTF8String]) == -1) {
+                LOG_SYS("Could not unlink tile " << [[tile path] UTF8String]);
+            }
+            return;
         }
 
         const char *buf = [message.body UTF8String];
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 1f2dcf047..9b9b3c236 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -794,7 +794,7 @@ L.Socket = L.Class.extend({
 			}
 		}
 		else if (window.ThisIsTheiOSApp) {
-			// In the iOS app, the native code sends us the PNG tile already as a data: URL after the newline
+			// In the iOS app, the native code sends us the URL of the BMP for the tile after the newline
 			var newlineIndex = textMsg.indexOf('\n');
 			if (newlineIndex > 0) {
 				img = textMsg.substring(newlineIndex+1);
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 198b511f1..e19626eb2 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -2066,6 +2066,9 @@ L.TileLayer = L.GridLayer.extend({
 
 	_tileOnLoad: function (done, tile) {
 		done(null, tile);
+		if (window.ThisIsTheiOSApp) {
+			window.webkit.messageHandlers.lool.postMessage('REMOVE ' + tile.src, '*');
+		}
 	},
 
 	_tileOnError: function (done, tile, e) {


More information about the Libreoffice-commits mailing list