1 /** 2 re-implementation of the GermSerk ColorBleedingEffect texture padding algorithm 3 https://github.com/gemserk/imageprocessing/blob/master/src/main/java/com/gemserk/utils/imageprocessing/ColorBleedingEffect.java 4 5 This allows textures to be padded in such a way that there'll be no artifacts. 6 */ 7 module creator.core.colorbleed; 8 import inochi2d.core.texture; 9 10 /** 11 Does texture bleeding on a texture. 12 */ 13 void incColorBleedPixels(ShallowTexture* texture, int maxIterations = 32) { 14 int width = texture.width; 15 int height = texture.height; 16 17 union TexData { 18 ubyte[] bytes; 19 Pixel[] pixels; 20 } 21 22 TexData textureData = TexData(texture.data); 23 Mask* mask = new Mask(textureData.bytes); 24 25 int iterations = 0; 26 int lastPending = -1; 27 while (mask.getPendingSize > 0) { 28 if (iterations >= maxIterations) break; 29 30 lastPending = mask.getPendingSize(); 31 executeIteration(textureData.pixels, mask, width, height); 32 iterations++; 33 34 // Break on infinite loop. 35 if (mask.getPendingSize == lastPending) break; 36 } 37 38 texture.data = textureData.bytes; 39 } 40 41 /** 42 Does texture bleeding on a texture. 43 */ 44 void incColorBleedPixels(Texture texture, int maxIterations = 32) { 45 int width = texture.width; 46 int height = texture.height; 47 48 union TexData { 49 ubyte[] bytes; 50 Pixel[] pixels; 51 } 52 53 TexData textureData = TexData(texture.getTextureData()); 54 Mask* mask = new Mask(textureData.bytes); 55 56 int iterations = 0; 57 int lastPending = -1; 58 while (mask.getPendingSize > 0) { 59 if (iterations >= maxIterations) break; 60 61 lastPending = mask.getPendingSize(); 62 executeIteration(textureData.pixels, mask, width, height); 63 iterations++; 64 65 // Break on infinite loop. 66 if (mask.getPendingSize == lastPending) break; 67 } 68 69 texture.setData(textureData.bytes); 70 } 71 72 private: 73 enum TOPROCESS = 0; 74 enum INPROCESS = 1; 75 enum PIXELDATA = 2; 76 77 78 struct Pixel { 79 align(1): 80 ubyte r; 81 ubyte g; 82 ubyte b; 83 ubyte a; 84 } 85 86 struct Mask { 87 ubyte[] data; 88 size_t[] pending; 89 size_t[] changing; 90 91 this(ubyte[] texture) { 92 ubyte[] colorData = texture; 93 data = new ubyte[colorData.length/4]; 94 95 foreach(i; 0..data.length) { 96 size_t aRed = i*4; 97 size_t aGreen = aRed+1; 98 size_t aBlue = aRed+2; 99 size_t aAlpha = aRed+3; 100 Pixel data = Pixel( 101 colorData[aRed], 102 colorData[aGreen], 103 colorData[aBlue], 104 colorData[aAlpha], 105 ); 106 107 if (data.a == 0) { 108 this.data[i] = TOPROCESS; 109 this.pending ~= i; 110 } else { 111 this.data[i] = PIXELDATA; 112 } 113 } 114 } 115 116 int getPendingSize() { 117 return cast(int)pending.length; 118 } 119 120 ubyte getMask(size_t index) { 121 return data[index]; 122 } 123 124 size_t removeIndex(size_t index) { 125 if (index >= pending.length) { 126 throw new Exception("Out of bounds write to mask"); 127 } 128 129 size_t value = pending[index]; 130 pending[index] = pending[$-1]; 131 pending.length--; 132 133 return value; 134 } 135 } 136 137 struct MaskIterator { 138 Mask* mask; 139 size_t index; 140 141 this(Mask* mask) { 142 this.mask = mask; 143 } 144 145 bool hasNext() { 146 return index < mask.getPendingSize; 147 } 148 149 int next() { 150 return cast(int)mask.pending[index++]; 151 } 152 153 void markAsInProgress() { 154 index--; 155 size_t removed = mask.removeIndex(index); 156 mask.changing ~= removed; 157 } 158 159 void reset() { 160 index = 0; 161 foreach(i; 0..mask.changing.length) { 162 size_t index = mask.changing[i]; 163 mask.data[index] = PIXELDATA; 164 } 165 mask.changing.length = 0; 166 } 167 } 168 169 void executeIteration(Pixel[] rgba, Mask* mask, int width, int height) { 170 int[2][8] offsets = [ 171 [-1, -1], 172 [0, -1], 173 [1, -1], 174 [-1, 0], 175 [1, 0], 176 [-1, 1], 177 [0, 1], 178 [1, 1], 179 ]; 180 181 182 183 MaskIterator iterator = MaskIterator(mask); 184 while(iterator.hasNext) { 185 int pixelIndex = iterator.next; 186 187 int x = pixelIndex % width; 188 int y = pixelIndex / width; 189 190 int r = 0; 191 int g = 0; 192 int b = 0; 193 int cant = 0; 194 195 foreach(i, offset; offsets) { 196 int column = x + offset[0]; 197 int row = y + offset[1]; 198 199 if (column < 0 || column >= width || row < 0 || row >= height) continue; 200 201 int currentPixelIndex = row * width + column; 202 Pixel pixelData = rgba[currentPixelIndex]; 203 if (mask.getMask(currentPixelIndex) == PIXELDATA) { 204 r += pixelData.r; 205 g += pixelData.g; 206 b += pixelData.b; 207 cant++; 208 } 209 } 210 211 if (cant != 0) { 212 rgba[pixelIndex] = Pixel( 213 cast(ubyte)(r / cant), 214 cast(ubyte)(g / cant), 215 cast(ubyte)(b / cant), 216 0 217 ); 218 iterator.markAsInProgress(); 219 } 220 } 221 222 iterator.reset(); 223 }