1 module farbfelded; 2 3 private 4 { 5 import std.algorithm; 6 import std.conv; 7 import std.math; 8 import std.stdio; 9 import std.string; 10 11 template addProperty(T, string propertyName, string defaultValue = T.init.to!string) 12 { 13 const char[] addProperty = format( 14 ` 15 private %2$s %1$s = %4$s; 16 17 void set%3$s(%2$s %1$s) 18 { 19 this.%1$s = %1$s; 20 } 21 22 %2$s get%3$s() 23 { 24 return %1$s; 25 } 26 `, 27 "_" ~ propertyName.toLower, 28 T.stringof, 29 propertyName, 30 defaultValue 31 ); 32 } 33 34 enum BYTE_ORDER 35 { 36 LITTLE_ENDIAN, 37 BIG_ENDIAN 38 } 39 40 T buildFromBytes(T)(BYTE_ORDER byteOrder, ubyte[] bytes...) 41 { 42 T mask; 43 size_t shift; 44 45 foreach (i, e; bytes) 46 { 47 final switch (byteOrder) with (BYTE_ORDER) 48 { 49 case LITTLE_ENDIAN: 50 shift = (i << 3); 51 break; 52 case BIG_ENDIAN: 53 shift = ((bytes.length - i - 1) << 3); 54 break; 55 } 56 mask |= (e << shift); 57 } 58 59 return mask; 60 } 61 62 auto buildFromValue(T)(BYTE_ORDER byteOrder, T value) 63 { 64 ubyte[] data; 65 T mask = cast(T) 0xff; 66 size_t shift; 67 68 foreach (i; 0..T.sizeof) 69 { 70 final switch (byteOrder) with (BYTE_ORDER) 71 { 72 case LITTLE_ENDIAN: 73 shift = (i << 3); 74 break; 75 case BIG_ENDIAN: 76 shift = ((T.sizeof - i - 1) << 3); 77 break; 78 } 79 80 data ~= cast(ubyte) ( 81 (value & (mask << shift)) >> shift 82 ); 83 } 84 85 return data; 86 } 87 } 88 89 90 class RGBAColor 91 { 92 mixin(addProperty!(int, "R")); 93 mixin(addProperty!(int, "G")); 94 mixin(addProperty!(int, "B")); 95 mixin(addProperty!(int, "A")); 96 97 this(int R = 0, int G = 0, int B = 0, int A = 0) 98 { 99 this._r = R; 100 this._g = G; 101 this._b = B; 102 this._a = A; 103 } 104 105 const float luminance709() 106 { 107 return (_r * 0.2126f + _g * 0.7152f + _b * 0.0722f); 108 } 109 110 const float luminance601() 111 { 112 return (_r * 0.3f + _g * 0.59f + _b * 0.11f); 113 } 114 115 const float luminanceAverage() 116 { 117 return (_r + _g + _b) / 3.0; 118 } 119 120 alias luminance = luminance709; 121 122 override string toString() 123 { 124 return format("RGBAColor(%d, %d, %d, %d, I = %f)", _r, _g, _b, _a, this.luminance); 125 } 126 127 RGBAColor opBinary(string op, T)(auto ref T rhs) 128 { 129 return mixin( 130 format(`new RGBAColor( 131 clamp(cast(int) (_r %1$s rhs), 0, 65535), 132 clamp(cast(int) (_g %1$s rhs), 0, 65535), 133 clamp(cast(int) (_b %1$s rhs), 0, 65535), 134 clamp(cast(int) (_a %1$s rhs), 0, 65535) 135 ) 136 `, 137 op 138 ) 139 ); 140 } 141 142 RGBAColor opBinary(string op)(RGBAColor rhs) 143 { 144 return mixin( 145 format(`new RGBAColor( 146 clamp(cast(int) (_r %1$s rhs.getR), 0, 65535), 147 clamp(cast(int) (_g %1$s rhs.getG), 0, 65535), 148 clamp(cast(int) (_b %1$s rhs.getB), 0, 65535), 149 clamp(cast(int) (_a %1$s rhs.getA), 0, 65535) 150 ) 151 `, 152 op 153 ) 154 ); 155 } 156 } 157 158 159 class FarbfeldImage 160 { 161 mixin(addProperty!(uint, "Width")); 162 mixin(addProperty!(uint, "Height")); 163 164 private 165 { 166 RGBAColor[] _image; 167 168 auto actualIndex(size_t i) 169 { 170 auto S = _width * _height; 171 172 return clamp(i, 0, S - 1); 173 } 174 175 auto actualIndex(size_t i, size_t j) 176 { 177 auto W = cast(size_t) clamp(i, 0, _width - 1); 178 auto H = cast(size_t) clamp(j, 0, _height - 1); 179 auto S = _width * _height; 180 181 return clamp(W + H * _width, 0, S); 182 } 183 } 184 185 this(uint width = 0, uint height = 0, RGBAColor color = new RGBAColor(0, 0, 0, 0)) 186 { 187 this._width = width; 188 this._height = height; 189 190 foreach (x; 0.._width) 191 { 192 foreach (y; 0.._height) 193 { 194 _image ~= color; 195 } 196 } 197 } 198 199 RGBAColor opIndexAssign(RGBAColor color, size_t x, size_t y) 200 { 201 _image[actualIndex(x, y)] = color; 202 return color; 203 } 204 205 RGBAColor opIndexAssign(RGBAColor color, size_t x) 206 { 207 _image[actualIndex(x)] = color; 208 return color; 209 } 210 211 RGBAColor opIndex(size_t x, size_t y) 212 { 213 return _image[actualIndex(x, y)]; 214 } 215 216 RGBAColor opIndex(size_t x) 217 { 218 return _image[actualIndex(x)]; 219 } 220 221 override string toString() 222 { 223 string accumulator = "["; 224 225 foreach (x; 0.._width) 226 { 227 string tmp = "["; 228 foreach (y; 0.._height) 229 { 230 tmp ~= _image[actualIndex(x, y)].toString ~ ", "; 231 } 232 tmp = tmp[0..$-2] ~ "], "; 233 accumulator ~= tmp; 234 } 235 return accumulator[0..$-2] ~ "]"; 236 } 237 238 alias width = getWidth; 239 alias height = getHeight; 240 241 final RGBAColor[] array() 242 { 243 return _image; 244 } 245 246 final void array(RGBAColor[] image) 247 { 248 _image = image; 249 } 250 251 final void changeCapacity(uint x, uint y) 252 { 253 long newLength = (x * y); 254 255 if (newLength > _image.length) 256 { 257 auto restLength = cast(long) newLength - _image.length; 258 _image.length += cast(size_t) restLength; 259 } 260 else 261 { 262 if (newLength < _image.length) 263 { 264 auto restLength = cast(long) _image.length - newLength; 265 _image.length -= cast(size_t) restLength; 266 } 267 } 268 _width = x; 269 _height = y; 270 } 271 272 273 void load(string filename) 274 { 275 File file; 276 file.open(filename, `rb`); 277 278 // magic number is `farbfeld` (field size: 8 bytes) 279 auto magicNumber = new void[8]; 280 file.rawRead!void(magicNumber); 281 // image width (field size: 4 bytes) and image height (field size: 4 bytes) 282 auto imageSizes = new ubyte[8]; 283 file.rawRead!ubyte(imageSizes); 284 285 _width = buildFromBytes!uint(BYTE_ORDER.BIG_ENDIAN, imageSizes[0..4]); 286 _height = buildFromBytes!uint(BYTE_ORDER.BIG_ENDIAN, imageSizes[4..$]); 287 _image = []; 288 289 foreach (i; 0.._width) 290 { 291 foreach (j; 0.._height) 292 { 293 auto pixel = new ubyte[8]; 294 file.rawRead!ubyte(pixel); 295 296 auto R = buildFromBytes!ushort(BYTE_ORDER.BIG_ENDIAN, pixel[0..2]); 297 auto G = buildFromBytes!ushort(BYTE_ORDER.BIG_ENDIAN, pixel[2..4]); 298 auto B = buildFromBytes!ushort(BYTE_ORDER.BIG_ENDIAN, pixel[4..6]); 299 auto A = buildFromBytes!ushort(BYTE_ORDER.BIG_ENDIAN, pixel[6..$]); 300 301 _image ~= new RGBAColor(R, G, B, A); 302 } 303 } 304 } 305 306 307 void save(string filename) 308 { 309 File file; 310 file.open(filename, `wb`); 311 312 // magic number 313 file.write(`farbfeld`); 314 //width 315 file.write(cast(char[]) buildFromValue!uint(BYTE_ORDER.BIG_ENDIAN, _width)); 316 // height 317 file.write(cast(char[]) buildFromValue!uint(BYTE_ORDER.BIG_ENDIAN, _height)); 318 319 foreach (i; 0..(_width * _height)) 320 { 321 auto pixel = _image[i]; 322 323 auto R = buildFromValue!ushort(BYTE_ORDER.BIG_ENDIAN, cast(ushort) pixel.getR); 324 auto G = buildFromValue!ushort(BYTE_ORDER.BIG_ENDIAN, cast(ushort) pixel.getG); 325 auto B = buildFromValue!ushort(BYTE_ORDER.BIG_ENDIAN, cast(ushort) pixel.getB); 326 auto A = buildFromValue!ushort(BYTE_ORDER.BIG_ENDIAN, cast(ushort) pixel.getA); 327 328 file.write(cast(char[]) R); 329 file.write(cast(char[]) G); 330 file.write(cast(char[]) B); 331 file.write(cast(char[]) A); 332 } 333 } 334 }