1 module dplug.gui.screencap; 2 3 import dplug.core.vec; 4 import dplug.core.nogc; 5 import dplug.core.binrange; 6 import dplug.graphics.image; 7 import dplug.window; 8 9 import core.stdc.stdio; 10 import core.stdc.string; 11 12 nothrow @nogc: 13 14 15 /// Create a 3D voxel file representing the whole UI. 16 /// Export to a .qb file, as used as input by Qubicle, Goxel, and https://drububu.com/miscellaneous/voxelizer/ 17 /// Use https://drububu.com/miscellaneous/voxelizer/ if you want to convert to a MagicaVoxel .vox 18 /// Alternatively: https://github.com/mgerhardy/vengi 19 /// Free the result with `free(slice.ptr)`. 20 ubyte[] encodeScreenshotAsQB(ImageRef!RGBA colorMap, 21 WindowPixelFormat pf, // input pixel format 22 ImageRef!L16 depthMap) 23 { 24 int DEPTH = 16; 25 int ADD_DEPTH = 10; // Additional depth voxels, so that the plugin is more tight. 26 27 int W = colorMap.w; 28 int H = colorMap.h; 29 30 Vec!ubyte vox = makeVec!ubyte; 31 32 vox.writeLE!ubyte(1); // .qb version 33 vox.writeLE!ubyte(1); 34 vox.writeLE!ubyte(0); 35 vox.writeLE!ubyte(0); 36 vox.writeLE!uint(0); // RGBA 37 vox.writeLE!uint(0); // left handed 38 vox.writeLE!uint(0); // uncompressed 39 vox.writeLE!uint(0); // alpha is 0 or 255, tells visibility 40 vox.writeLE!uint(1); // one matrice in file 41 vox.writeLE!ubyte(1); // matrix name 42 vox.writeLE!ubyte('0'); 43 44 // read matrix size 45 vox.writeLE(W); 46 vox.writeLE(DEPTH + ADD_DEPTH); 47 vox.writeLE(H); 48 49 vox.writeLE(0); 50 vox.writeLE(0); 51 vox.writeLE(0); // position 52 53 for (int z = 0; z < H; z++) 54 { 55 // y inverted in .vox vs screen 56 L16[] depthScan = depthMap.scanline(z); 57 58 for (int y = 0; y < DEPTH + ADD_DEPTH; y++) 59 { 60 for (int x = 0; x < W; x++) 61 { 62 L16 depthHere = depthScan[x]; 63 // note: in magickavoxel, increasing depth is NOT towards viewer 64 int depth = ADD_DEPTH + (DEPTH * depthHere.l) / 65536; 65 RGBA color = colorMap[x, z]; 66 vox.writeLE!ubyte(color.r); 67 vox.writeLE!ubyte(color.g); 68 vox.writeLE!ubyte(color.b); 69 if (depth >= y) 70 vox.writeLE!ubyte(255); 71 else 72 vox.writeLE!ubyte(0); 73 } 74 } 75 } 76 return vox.releaseData; 77 } 78 79 /// Create a PNG screenshot of the whole UI. 80 /// Free the result with `free(slice.ptr)`. 81 ubyte[] encodeScreenshotAsPNG(ImageRef!RGBA colorMap, WindowPixelFormat pf) 82 { 83 import gamut; 84 Image source; 85 source.createViewFromImageRef!RGBA(colorMap); 86 87 // make a clone to own the memory 88 Image image = source.clone(); 89 90 assert(image.type == PixelType.rgba8); 91 assert(image.hasData); 92 93 static void swapByte(ref ubyte a, ref ubyte b) 94 { 95 ubyte tmp = a; 96 a = b; 97 b = tmp; 98 } 99 100 final switch(pf) 101 { 102 case WindowPixelFormat.RGBA8: break; 103 case WindowPixelFormat.BGRA8: 104 for (int y = 0; y < image.height(); ++y) 105 { 106 ubyte* scan = cast(ubyte*) image.scanptr(y); 107 for (int x = 0; x < image.width(); ++x) 108 { 109 swapByte(scan[4*x + 0], scan[4*x + 2]); 110 } 111 } 112 break; 113 case WindowPixelFormat.ARGB8: 114 for (int y = 0; y < image.height(); ++y) 115 { 116 ubyte* scan = cast(ubyte*) image.scanptr(y); 117 for (int x = 0; x < image.width(); ++x) 118 { 119 ubyte a = scan[4*x + 0]; 120 ubyte r = scan[4*x + 1]; 121 ubyte g = scan[4*x + 2]; 122 ubyte b = scan[4*x + 3]; 123 scan[4*x + 0] = r; 124 scan[4*x + 1] = g; 125 scan[4*x + 2] = b; 126 scan[4*x + 3] = a; 127 } 128 } 129 break; 130 } 131 132 ubyte[] png = image.saveToMemory(ImageFormat.PNG); 133 scope(exit) freeEncodedImage(png); 134 135 // Return a duped slice because of different freeing functions 136 return mallocDup(png); 137 }