1 /// this file imports all available image decoders, and provides convenient functions to load image regardless of it's format. 2 module arsd.image; 3 4 public import arsd.color; 5 public import arsd.png; 6 public import arsd.jpeg; 7 public import arsd.bmp; 8 public import arsd.targa; 9 public import arsd.pcx; 10 public import arsd.dds; 11 12 import core.memory; 13 14 static if (__traits(compiles, { import iv.vfs; })) enum ArsdImageHasIVVFS = true; else enum ArsdImageHasIVVFS = false; 15 16 17 private bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 18 if (s0.length != s1.length) return false; 19 foreach (immutable idx, char ch; s0) { 20 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower() 21 char c1 = s1.ptr[idx]; 22 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower() 23 if (ch != c1) return false; 24 } 25 return true; 26 } 27 28 29 /// Image formats `arsd.image` can load (except `Unknown`, of course). 30 enum ImageFileFormat { 31 Unknown, /// 32 Png, /// 33 Bmp, /// 34 Jpeg, /// 35 Tga, /// 36 Gif, /// we can't load it yet, but we can at least detect it 37 Pcx, /// can load 8BPP and 24BPP pcx images 38 Dds, /// can load ARGB8888, DXT1, DXT3, DXT5 dds images (without mipmaps) 39 } 40 41 42 /// Try to guess image format from file extension. 43 public ImageFileFormat guessImageFormatFromExtension (const(char)[] filename) { 44 if (filename.length < 2) return ImageFileFormat.Unknown; 45 size_t extpos = filename.length; 46 version(Windows) { 47 while (extpos > 0 && filename.ptr[extpos-1] != '.' && filename.ptr[extpos-1] != '/' && filename.ptr[extpos-1] != '\\' && filename.ptr[extpos-1] != ':') --extpos; 48 } else { 49 while (extpos > 0 && filename.ptr[extpos-1] != '.' && filename.ptr[extpos-1] != '/') --extpos; 50 } 51 if (extpos == 0 || filename.ptr[extpos-1] != '.') return ImageFileFormat.Unknown; 52 auto ext = filename[extpos..$]; 53 if (strEquCI(ext, "png")) return ImageFileFormat.Png; 54 if (strEquCI(ext, "bmp")) return ImageFileFormat.Bmp; 55 if (strEquCI(ext, "jpg") || strEquCI(ext, "jpeg")) return ImageFileFormat.Jpeg; 56 if (strEquCI(ext, "gif")) return ImageFileFormat.Gif; 57 if (strEquCI(ext, "tga")) return ImageFileFormat.Tga; 58 if (strEquCI(ext, "pcx")) return ImageFileFormat.Pcx; 59 if (strEquCI(ext, "dds")) return ImageFileFormat.Dds; 60 return ImageFileFormat.Unknown; 61 } 62 63 64 /// Try to guess image format by first data bytes. 65 public ImageFileFormat guessImageFormatFromMemory (const(void)[] membuf) { 66 enum TargaSign = "TRUEVISION-XFILE.\x00"; 67 auto buf = cast(const(ubyte)[])membuf; 68 if (buf.length == 0) return ImageFileFormat.Unknown; 69 // detect file format 70 // png 71 if (buf.length > 7 && buf.ptr[0] == 0x89 && buf.ptr[1] == 0x50 && buf.ptr[2] == 0x4E && 72 buf.ptr[3] == 0x47 && buf.ptr[4] == 0x0D && buf.ptr[5] == 0x0A && buf.ptr[6] == 0x1A) 73 { 74 return ImageFileFormat.Png; 75 } 76 // bmp 77 if (buf.length > 6 && buf.ptr[0] == 'B' && buf.ptr[1] == 'M') { 78 uint datasize = buf.ptr[2]|(buf.ptr[3]<<8)|(buf.ptr[4]<<16)|(buf.ptr[5]<<24); 79 if (datasize > 6 && datasize <= buf.length) return ImageFileFormat.Bmp; 80 } 81 // gif 82 if (buf.length > 5 && buf.ptr[0] == 'G' && buf.ptr[1] == 'I' && buf.ptr[2] == 'F' && 83 buf.ptr[3] == '8' && (buf.ptr[4] == '7' || buf.ptr[4] == '9')) 84 { 85 return ImageFileFormat.Gif; 86 } 87 // dds 88 if (ddsDetect(membuf)) return ImageFileFormat.Dds; 89 // jpg 90 try { 91 int width, height, components; 92 if (detect_jpeg_image_from_memory(buf, width, height, components)) return ImageFileFormat.Jpeg; 93 } catch (Exception e) {} // sorry 94 // tga (sorry, targas without footer, i don't love you) 95 if (buf.length > TargaSign.length+4*2 && cast(const(char)[])(buf[$-TargaSign.length..$]) == TargaSign) { 96 // more guesswork 97 switch (buf.ptr[2]) { 98 case 1: case 2: case 3: case 9: case 10: case 11: return ImageFileFormat.Tga; 99 default: 100 } 101 } 102 // ok, try to guess targa by validating some header fields 103 bool guessTarga () nothrow @trusted @nogc { 104 if (buf.length < 45) return false; // minimal 1x1 tga 105 immutable ubyte idlength = buf.ptr[0]; 106 immutable ubyte bColorMapType = buf.ptr[1]; 107 immutable ubyte type = buf.ptr[2]; 108 immutable ushort wColorMapFirstEntryIndex = cast(ushort)(buf.ptr[3]|(buf.ptr[4]<<8)); 109 immutable ushort wColorMapLength = cast(ushort)(buf.ptr[5]|(buf.ptr[6]<<8)); 110 immutable ubyte bColorMapEntrySize = buf.ptr[7]; 111 immutable ushort wOriginX = cast(ushort)(buf.ptr[8]|(buf.ptr[9]<<8)); 112 immutable ushort wOriginY = cast(ushort)(buf.ptr[10]|(buf.ptr[11]<<8)); 113 immutable ushort wImageWidth = cast(ushort)(buf.ptr[12]|(buf.ptr[13]<<8)); 114 immutable ushort wImageHeight = cast(ushort)(buf.ptr[14]|(buf.ptr[15]<<8)); 115 immutable ubyte bPixelDepth = buf.ptr[16]; 116 immutable ubyte bImageDescriptor = buf.ptr[17]; 117 if (wImageWidth < 1 || wImageHeight < 1 || wImageWidth > 32000 || wImageHeight > 32000) return false; // arbitrary limit 118 immutable uint pixelsize = (bPixelDepth>>3); 119 switch (type) { 120 case 2: // truecolor, raw 121 case 10: // truecolor, rle 122 switch (pixelsize) { 123 case 2: case 3: case 4: break; 124 default: return false; 125 } 126 break; 127 case 1: // paletted, raw 128 case 9: // paletted, rle 129 if (pixelsize != 1) return false; 130 break; 131 case 3: // b/w, raw 132 case 11: // b/w, rle 133 if (pixelsize != 1 && pixelsize != 2) return false; 134 break; 135 default: // invalid type 136 return false; 137 } 138 // check for valid colormap 139 switch (bColorMapType) { 140 case 0: 141 if (wColorMapFirstEntryIndex != 0 || wColorMapLength != 0) return 0; 142 break; 143 case 1: 144 if (bColorMapEntrySize != 15 && bColorMapEntrySize != 16 && bColorMapEntrySize != 24 && bColorMapEntrySize != 32) return false; 145 if (wColorMapLength == 0) return false; 146 break; 147 default: // invalid colormap type 148 return false; 149 } 150 if (((bImageDescriptor>>6)&3) != 0) return false; 151 // this *looks* like a tga 152 return true; 153 } 154 if (guessTarga()) return ImageFileFormat.Tga; 155 156 bool guessPcx() nothrow @trusted @nogc { 157 if (buf.length < 129) return false; // we should have at least header 158 159 ubyte manufacturer = buf.ptr[0]; 160 ubyte ver = buf.ptr[1]; 161 ubyte encoding = buf.ptr[2]; 162 ubyte bitsperpixel = buf.ptr[3]; 163 ushort xmin = cast(ushort)(buf.ptr[4]+256*buf.ptr[5]); 164 ushort ymin = cast(ushort)(buf.ptr[6]+256*buf.ptr[7]); 165 ushort xmax = cast(ushort)(buf.ptr[8]+256*buf.ptr[9]); 166 ushort ymax = cast(ushort)(buf.ptr[10]+256*buf.ptr[11]); 167 ubyte reserved = buf.ptr[64]; 168 ubyte colorplanes = buf.ptr[65]; 169 ushort bytesperline = cast(ushort)(buf.ptr[66]+256*buf.ptr[67]); 170 //ushort palettetype = cast(ushort)(buf.ptr[68]+256*buf.ptr[69]); 171 172 // check some header fields 173 if (manufacturer != 0x0a) return false; 174 if (/*ver != 0 && ver != 2 && ver != 3 &&*/ ver != 5) return false; 175 if (encoding != 0 && encoding != 1) return false; 176 177 int wdt = xmax-xmin+1; 178 int hgt = ymax-ymin+1; 179 180 // arbitrary size limits 181 if (wdt < 1 || wdt > 32000) return false; 182 if (hgt < 1 || hgt > 32000) return false; 183 184 if (bytesperline < wdt) return false; 185 186 // if it's not a 256-color PCX file, and not 24-bit PCX file, gtfo 187 bool bpp24 = false; 188 if (colorplanes == 1) { 189 if (bitsperpixel != 8 && bitsperpixel != 24 && bitsperpixel != 32) return false; 190 bpp24 = (bitsperpixel == 24); 191 } else if (colorplanes == 3 || colorplanes == 4) { 192 if (bitsperpixel != 8) return false; 193 bpp24 = true; 194 } 195 196 // additional checks 197 if (reserved != 0) return false; 198 199 // 8bpp files MUST have palette 200 if (!bpp24 && buf.length < 129+769) return false; 201 202 // it can be pcx 203 return true; 204 } 205 if (guessPcx()) return ImageFileFormat.Pcx; 206 207 // dunno 208 return ImageFileFormat.Unknown; 209 } 210 211 212 /// Try to guess image format from file name and load that image. 213 public MemoryImage loadImageFromFile(T:const(char)[]) (T filename) { 214 static if (is(T == typeof(null))) { 215 throw new Exception("cannot load image from unnamed file"); 216 } else { 217 final switch (guessImageFormatFromExtension(filename)) { 218 case ImageFileFormat.Unknown: 219 //throw new Exception("cannot determine file format from extension"); 220 static if (ArsdImageHasIVVFS) { 221 auto fl = VFile(filename); 222 } else { 223 import std.stdio; 224 static if (is(T == string)) { 225 auto fl = File(filename); 226 } else { 227 auto fl = File(filename.idup); 228 } 229 } 230 auto fsz = fl.size-fl.tell; 231 if (fsz < 4) throw new Exception("cannot determine file format"); 232 if (fsz > int.max/8) throw new Exception("image data too big"); 233 auto data = new ubyte[](cast(uint)fsz); 234 scope(exit) { import core.memory : GC; GC.free(data.ptr); } // this should be safe, as image will copy data to it's internal storage 235 fl.rawReadExact(data); 236 return loadImageFromMemory(data); 237 case ImageFileFormat.Png: static if (is(T == string)) return readPng(filename); else return readPng(filename.idup); 238 case ImageFileFormat.Bmp: static if (is(T == string)) return readBmp(filename); else return readBmp(filename.idup); 239 case ImageFileFormat.Jpeg: return readJpeg(filename); 240 case ImageFileFormat.Gif: throw new Exception("arsd has no GIF loader yet"); 241 case ImageFileFormat.Tga: return loadTga(filename); 242 case ImageFileFormat.Pcx: return loadPcx(filename); 243 case ImageFileFormat.Dds: 244 static if (ArsdImageHasIVVFS) { 245 auto fl = VFile(filename); 246 } else { 247 import std.stdio; 248 static if (is(T == string)) { 249 auto fl = File(filename); 250 } else { 251 auto fl = File(filename.idup); 252 } 253 } 254 return ddsLoadFromFile(fl); 255 } 256 } 257 } 258 259 260 /// Try to guess image format from data and load that image. 261 public MemoryImage loadImageFromMemory (const(void)[] membuf) { 262 final switch (guessImageFormatFromMemory(membuf)) { 263 case ImageFileFormat.Unknown: throw new Exception("cannot determine file format"); 264 case ImageFileFormat.Png: return imageFromPng(readPng(cast(const(ubyte)[])membuf)); 265 case ImageFileFormat.Bmp: return readBmp(cast(const(ubyte)[])membuf); 266 case ImageFileFormat.Jpeg: return readJpegFromMemory(cast(const(ubyte)[])membuf); 267 case ImageFileFormat.Gif: throw new Exception("arsd has no GIF loader yet"); 268 case ImageFileFormat.Tga: return loadTgaMem(membuf); 269 case ImageFileFormat.Pcx: return loadPcxMem(membuf); 270 case ImageFileFormat.Dds: return ddsLoadFromMemory(membuf); 271 } 272 } 273 274 275 static if (ArsdImageHasIVVFS) { 276 import iv.vfs; 277 public MemoryImage loadImageFromFile (VFile fl) { 278 auto fsz = fl.size-fl.tell; 279 if (fsz < 4) throw new Exception("cannot determine file format"); 280 if (fsz > int.max/8) throw new Exception("image data too big"); 281 auto data = new ubyte[](cast(uint)fsz); 282 scope(exit) { import core.memory : GC; GC.free(data.ptr); } // this should be safe, as image will copy data to it's internal storage 283 fl.rawReadExact(data); 284 return loadImageFromMemory(data); 285 } 286 } 287 288 289 // ////////////////////////////////////////////////////////////////////////// // 290 // Separable filtering image rescaler v2.21, Rich Geldreich - richgel99@gmail.com 291 // 292 // This is free and unencumbered software released into the public domain. 293 // 294 // Anyone is free to copy, modify, publish, use, compile, sell, or 295 // distribute this software, either in source code form or as a compiled 296 // binary, for any purpose, commercial or non-commercial, and by any 297 // means. 298 // 299 // In jurisdictions that recognize copyright laws, the author or authors 300 // of this software dedicate any and all copyright interest in the 301 // software to the public domain. We make this dedication for the benefit 302 // of the public at large and to the detriment of our heirs and 303 // successors. We intend this dedication to be an overt act of 304 // relinquishment in perpetuity of all present and future rights to this 305 // software under copyright law. 306 // 307 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 308 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 309 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 310 // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 311 // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 312 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 313 // OTHER DEALINGS IN THE SOFTWARE. 314 // 315 // For more information, please refer to <http://unlicense.org/> 316 // 317 // Feb. 1996: Creation, losely based on a heavily bugfixed version of Schumacher's resampler in Graphics Gems 3. 318 // Oct. 2000: Ported to C++, tweaks. 319 // May 2001: Continous to discrete mapping, box filter tweaks. 320 // March 9, 2002: Kaiser filter grabbed from Jonathan Blow's GD magazine mipmap sample code. 321 // Sept. 8, 2002: Comments cleaned up a bit. 322 // Dec. 31, 2008: v2.2: Bit more cleanup, released as public domain. 323 // June 4, 2012: v2.21: Switched to unlicense.org, integrated GCC fixes supplied by Peter Nagy <petern@crytek.com>, Anteru at anteru.net, and clay@coge.net, 324 // added Codeblocks project (for testing with MinGW and GCC), VS2008 static code analysis pass. 325 // float or double 326 private: 327 328 //version = iresample_debug; 329 330 331 // ////////////////////////////////////////////////////////////////////////// // 332 public enum ImageResizeDefaultFilter = "lanczos4"; /// Default filter for image resampler. 333 public enum ImageResizeMaxDimension = 65536; /// Maximum image width/height for image resampler. 334 335 336 // ////////////////////////////////////////////////////////////////////////// // 337 /// Number of known image resizer filters. 338 public @property int imageResizeFilterCount () { pragma(inline, true); return NumFilters; } 339 340 /// Get filter name. Will return `null` for invalid index. 341 public string imageResizeFilterName (long idx) { pragma(inline, true); return (idx >= 0 && idx < NumFilters ? gFilters.ptr[cast(uint)idx].name : null); } 342 343 /// Find filter index by name. Will use default filter for invalid names. 344 public int imageResizeFindFilter (const(char)[] name, const(char)[] defaultFilter=ImageResizeDefaultFilter) { 345 int res = resamplerFindFilterInternal(name); 346 if (res >= 0) return res; 347 res = resamplerFindFilterInternal(defaultFilter); 348 if (res >= 0) return res; 349 res = resamplerFindFilterInternal("lanczos4"); 350 assert(res >= 0); 351 return res; 352 } 353 354 355 // ////////////////////////////////////////////////////////////////////////// // 356 /// Resize image. 357 public TrueColorImage imageResize(int Components=4) (MemoryImage msrcimg, int dstwdt, int dsthgt, const(char)[] filter=null, float gamma=1.0f, float filterScale=1.0f) { 358 static assert(Components == 1 || Components == 3 || Components == 4, "invalid number of components in color"); 359 return imageResize!Components(msrcimg, dstwdt, dsthgt, imageResizeFindFilter(filter), gamma, filterScale); 360 } 361 362 /// Ditto. 363 public TrueColorImage imageResize(int Components=4) (MemoryImage msrcimg, int dstwdt, int dsthgt, int filter, float gamma=1.0f, float filterScale=1.0f) { 364 static assert(Components == 1 || Components == 3 || Components == 4, "invalid number of components in color"); 365 if (msrcimg is null || msrcimg.width < 1 || msrcimg.height < 1 || msrcimg.width > ImageResizeMaxDimension || msrcimg.height > ImageResizeMaxDimension) { 366 throw new Exception("invalid source image"); 367 } 368 if (dstwdt < 1 || dsthgt < 1 || dstwdt > ImageResizeMaxDimension || dsthgt > ImageResizeMaxDimension) throw new Exception("invalid destination image size"); 369 auto resimg = new TrueColorImage(dstwdt, dsthgt); 370 scope(failure) .destroy(resimg); 371 if (auto tc = cast(TrueColorImage)msrcimg) { 372 imageResize!Components( 373 delegate (Color[] destrow, int y) { destrow[] = tc.imageData.colors[y*tc.width..(y+1)*tc.width]; }, 374 delegate (int y, const(Color)[] row) { resimg.imageData.colors[y*resimg.width..(y+1)*resimg.width] = row[]; }, 375 msrcimg.width, msrcimg.height, dstwdt, dsthgt, filter, gamma, filterScale 376 ); 377 } else { 378 imageResize!Components( 379 delegate (Color[] destrow, int y) { foreach (immutable x, ref c; destrow) c = msrcimg.getPixel(cast(int)x, y); }, 380 delegate (int y, const(Color)[] row) { resimg.imageData.colors[y*resimg.width..(y+1)*resimg.width] = row[]; }, 381 msrcimg.width, msrcimg.height, dstwdt, dsthgt, filter, gamma, filterScale 382 ); 383 } 384 return resimg; 385 } 386 387 388 private { 389 enum Linear2srgbTableSize = 4096; 390 enum InvLinear2srgbTableSize = cast(float)(1.0f/Linear2srgbTableSize); 391 float[256] srgb2linear = void; 392 ubyte[Linear2srgbTableSize] linear2srgb = void; 393 float lastGamma = float.nan; 394 } 395 396 /// Resize image. 397 /// Partial gamma correction looks better on mips; set to 1.0 to disable gamma correction. 398 /// Filter scale: values < 1.0 cause aliasing, but create sharper looking mips (0.75f, for example). 399 public void imageResize(int Components=4) ( 400 scope void delegate (Color[] destrow, int y) srcGetRow, 401 scope void delegate (int y, const(Color)[] row) dstPutRow, 402 int srcwdt, int srchgt, int dstwdt, int dsthgt, 403 int filter=-1, float gamma=1.0f, float filterScale=1.0f 404 ) { 405 static assert(Components == 1 || Components == 3 || Components == 4, "invalid number of components in color"); 406 assert(srcGetRow !is null); 407 assert(dstPutRow !is null); 408 409 if (srcwdt < 1 || srchgt < 1 || dstwdt < 1 || dsthgt < 1 || 410 srcwdt > ImageResizeMaxDimension || srchgt > ImageResizeMaxDimension || 411 dstwdt > ImageResizeMaxDimension || dsthgt > ImageResizeMaxDimension) throw new Exception("invalid image size"); 412 413 if (filter < 0 || filter >= NumFilters) { 414 filter = resamplerFindFilterInternal(ImageResizeDefaultFilter); 415 if (filter < 0) { 416 filter = resamplerFindFilterInternal("lanczos4"); 417 } 418 } 419 assert(filter >= 0 && filter < NumFilters); 420 421 422 if (lastGamma != gamma) { 423 version(iresample_debug) { import core.stdc.stdio; stderr.fprintf("creating translation tables for gamma %f (previous gamma is %f)\n", gamma, lastGamma); } 424 foreach (immutable i, ref v; srgb2linear[]) { 425 import std.math : pow; 426 v = cast(float)pow(cast(int)i*1.0f/255.0f, gamma); 427 } 428 immutable float invSourceGamma = 1.0f/gamma; 429 foreach (immutable i, ref v; linear2srgb[]) { 430 import std.math : pow; 431 int k = cast(int)(255.0f*pow(cast(int)i*InvLinear2srgbTableSize, invSourceGamma)+0.5f); 432 if (k < 0) k = 0; else if (k > 255) k = 255; 433 v = cast(ubyte)k; 434 } 435 lastGamma = gamma; 436 } 437 version(iresample_debug) { import core.stdc.stdio; stderr.fprintf("filter is %d\n", filter); } 438 439 ImageResampleWorker[Components] resamplers; 440 float[][Components] samples; 441 Color[] srcrow, dstrow; 442 scope(exit) { 443 foreach (ref rsm; resamplers[]) .destroy(rsm); 444 foreach (ref smr; samples[]) .destroy(smr); 445 } 446 447 // now create a ImageResampleWorker instance for each component to process 448 // the first instance will create new contributor tables, which are shared by the resamplers 449 // used for the other components (a memory and slight cache efficiency optimization). 450 resamplers[0] = new ImageResampleWorker(srcwdt, srchgt, dstwdt, dsthgt, ImageResampleWorker.BoundaryClamp, 0.0f, 1.0f, filter, null, null, filterScale, filterScale); 451 samples[0].length = srcwdt; 452 srcrow.length = srcwdt; 453 dstrow.length = dstwdt; 454 foreach (immutable i; 1..Components) { 455 resamplers[i] = new ImageResampleWorker(srcwdt, srchgt, dstwdt, dsthgt, ImageResampleWorker.BoundaryClamp, 0.0f, 1.0f, filter, resamplers[0].getClistX(), resamplers[0].getClistY(), filterScale, filterScale); 456 samples[i].length = srcwdt; 457 } 458 459 int dsty = 0; 460 foreach (immutable int srcy; 0..srchgt) { 461 // get row components 462 srcGetRow(srcrow, srcy); 463 { 464 auto scp = srcrow.ptr; 465 foreach (immutable x; 0..srcwdt) { 466 auto sc = *scp++; 467 samples.ptr[0].ptr[x] = srgb2linear.ptr[sc.r]; // first component 468 static if (Components > 1) samples.ptr[1].ptr[x] = srgb2linear.ptr[sc.g]; // second component 469 static if (Components > 2) samples.ptr[2].ptr[x] = srgb2linear.ptr[sc.b]; // thirs component 470 static if (Components == 4) samples.ptr[3].ptr[x] = sc.a*(1.0f/255.0f); // fourth component is alpha, and it is already linear 471 } 472 } 473 474 foreach (immutable c; 0..Components) if (!resamplers.ptr[c].putLine(samples.ptr[c].ptr)) assert(0, "out of memory"); 475 476 for (;;) { 477 int compIdx = 0; 478 for (; compIdx < Components; ++compIdx) { 479 const(float)* outsmp = resamplers.ptr[compIdx].getLine(); 480 if (outsmp is null) break; 481 auto dsc = dstrow.ptr; 482 // alpha? 483 static if (Components == 4) { 484 if (compIdx == 3) { 485 foreach (immutable x; 0..dstwdt) { 486 dsc.a = Color.clampToByte(cast(int)(255.0f*(*outsmp++)+0.5f)); 487 ++dsc; 488 } 489 continue; 490 } 491 } 492 // color 493 auto dsb = (cast(ubyte*)dsc)+compIdx; 494 foreach (immutable x; 0..dstwdt) { 495 int j = cast(int)(Linear2srgbTableSize*(*outsmp++)+0.5f); 496 if (j < 0) j = 0; else if (j >= Linear2srgbTableSize) j = Linear2srgbTableSize-1; 497 *dsb = linear2srgb.ptr[j]; 498 dsb += 4; 499 } 500 } 501 if (compIdx < Components) break; 502 // fill destination line 503 assert(dsty < dsthgt); 504 static if (Components != 4) { 505 auto dsc = dstrow.ptr; 506 foreach (immutable x; 0..dstwdt) { 507 static if (Components == 1) dsc.g = dsc.b = dsc.r; 508 dsc.a = 255; 509 ++dsc; 510 } 511 } 512 //version(iresample_debug) { import core.stdc.stdio; stderr.fprintf("writing dest row %d with %u components\n", dsty, Components); } 513 dstPutRow(dsty, dstrow); 514 ++dsty; 515 } 516 } 517 } 518 519 520 // ////////////////////////////////////////////////////////////////////////// // 521 public final class ImageResampleWorker { 522 nothrow @trusted @nogc: 523 public: 524 alias ResampleReal = float; 525 alias Sample = ResampleReal; 526 527 static struct Contrib { 528 ResampleReal weight; 529 ushort pixel; 530 } 531 532 static struct ContribList { 533 ushort n; 534 Contrib* p; 535 } 536 537 alias BoundaryOp = int; 538 enum /*Boundary_Op*/ { 539 BoundaryWrap = 0, 540 BoundaryReflect = 1, 541 BoundaryClamp = 2, 542 } 543 544 alias Status = int; 545 enum /*Status*/ { 546 StatusOkay = 0, 547 StatusOutOfMemory = 1, 548 StatusBadFilterName = 2, 549 StatusScanBufferFull = 3, 550 } 551 552 private: 553 alias FilterFunc = ResampleReal function (ResampleReal) nothrow @trusted @nogc; 554 555 int mIntermediateX; 556 557 int mResampleSrcX; 558 int mResampleSrcY; 559 int mResampleDstX; 560 int mResampleDstY; 561 562 BoundaryOp mBoundaryOp; 563 564 Sample* mPdstBuf; 565 Sample* mPtmpBuf; 566 567 ContribList* mPclistX; 568 ContribList* mPclistY; 569 570 bool mClistXForced; 571 bool mClistYForced; 572 573 bool mDelayXResample; 574 575 int* mPsrcYCount; 576 ubyte* mPsrcYFlag; 577 578 // The maximum number of scanlines that can be buffered at one time. 579 enum MaxScanBufSize = ImageResizeMaxDimension; 580 581 static struct ScanBuf { 582 int[MaxScanBufSize] scanBufY; 583 Sample*[MaxScanBufSize] scanBufL; 584 } 585 586 ScanBuf* mPscanBuf; 587 588 int mCurSrcY; 589 int mCurDstY; 590 591 Status mStatus; 592 593 // The make_clist() method generates, for all destination samples, 594 // the list of all source samples with non-zero weighted contributions. 595 ContribList* makeClist( 596 int srcX, int dstX, BoundaryOp boundaryOp, 597 FilterFunc Pfilter, 598 ResampleReal filterSupport, 599 ResampleReal filterScale, 600 ResampleReal srcOfs) 601 { 602 import core.stdc.stdlib : calloc, free; 603 import std.math : floor, ceil; 604 605 static struct ContribBounds { 606 // The center of the range in DISCRETE coordinates (pixel center = 0.0f). 607 ResampleReal center; 608 int left, right; 609 } 610 611 ContribList* Pcontrib, PcontribRes; 612 Contrib* Pcpool; 613 Contrib* PcpoolNext; 614 ContribBounds* PcontribBounds; 615 616 if ((Pcontrib = cast(ContribList*)calloc(dstX, ContribList.sizeof)) is null) return null; 617 scope(exit) if (Pcontrib !is null) free(Pcontrib); 618 619 PcontribBounds = cast(ContribBounds*)calloc(dstX, ContribBounds.sizeof); 620 if (PcontribBounds is null) return null; 621 scope(exit) free(PcontribBounds); 622 623 enum ResampleReal NUDGE = 0.5f; 624 immutable ResampleReal ooFilterScale = 1.0f/filterScale; 625 immutable ResampleReal xscale = dstX/cast(ResampleReal)srcX; 626 627 if (xscale < 1.0f) { 628 int total = 0; 629 // Handle case when there are fewer destination samples than source samples (downsampling/minification). 630 // stretched half width of filter 631 immutable ResampleReal halfWidth = (filterSupport/xscale)*filterScale; 632 // Find the range of source sample(s) that will contribute to each destination sample. 633 foreach (immutable i; 0..dstX) { 634 // Convert from discrete to continuous coordinates, scale, then convert back to discrete. 635 ResampleReal center = (cast(ResampleReal)i+NUDGE)/xscale; 636 center -= NUDGE; 637 center += srcOfs; 638 immutable int left = castToInt(cast(ResampleReal)floor(center-halfWidth)); 639 immutable int right = castToInt(cast(ResampleReal)ceil(center+halfWidth)); 640 PcontribBounds[i].center = center; 641 PcontribBounds[i].left = left; 642 PcontribBounds[i].right = right; 643 total += (right-left+1); 644 } 645 646 // Allocate memory for contributors. 647 if (total == 0 || ((Pcpool = cast(Contrib*)calloc(total, Contrib.sizeof)) is null)) return null; 648 //scope(failure) free(Pcpool); 649 //immutable int total = n; 650 651 PcpoolNext = Pcpool; 652 653 // Create the list of source samples which contribute to each destination sample. 654 foreach (immutable i; 0..dstX) { 655 int maxK = -1; 656 ResampleReal maxW = -1e+20f; 657 658 ResampleReal center = PcontribBounds[i].center; 659 immutable int left = PcontribBounds[i].left; 660 immutable int right = PcontribBounds[i].right; 661 662 Pcontrib[i].n = 0; 663 Pcontrib[i].p = PcpoolNext; 664 PcpoolNext += (right-left+1); 665 assert(PcpoolNext-Pcpool <= total); 666 667 ResampleReal totalWeight0 = 0; 668 foreach (immutable j; left..right+1) totalWeight0 += Pfilter((center-cast(ResampleReal)j)*xscale*ooFilterScale); 669 immutable ResampleReal norm = cast(ResampleReal)(1.0f/totalWeight0); 670 671 ResampleReal totalWeight1 = 0; 672 foreach (immutable j; left..right+1) { 673 immutable ResampleReal weight = Pfilter((center-cast(ResampleReal)j)*xscale*ooFilterScale)*norm; 674 if (weight == 0.0f) continue; 675 immutable int n = reflect(j, srcX, boundaryOp); 676 // Increment the number of source samples which contribute to the current destination sample. 677 immutable int k = Pcontrib[i].n++; 678 Pcontrib[i].p[k].pixel = cast(ushort)(n); // store src sample number 679 Pcontrib[i].p[k].weight = weight; // store src sample weight 680 totalWeight1 += weight; // total weight of all contributors 681 if (weight > maxW) { 682 maxW = weight; 683 maxK = k; 684 } 685 } 686 //assert(Pcontrib[i].n); 687 //assert(max_k != -1); 688 if (maxK == -1 || Pcontrib[i].n == 0) return null; 689 if (totalWeight1 != 1.0f) Pcontrib[i].p[maxK].weight += 1.0f-totalWeight1; 690 } 691 } else { 692 int total = 0; 693 // Handle case when there are more destination samples than source samples (upsampling). 694 immutable ResampleReal halfWidth = filterSupport*filterScale; 695 // Find the source sample(s) that contribute to each destination sample. 696 foreach (immutable i; 0..dstX) { 697 // Convert from discrete to continuous coordinates, scale, then convert back to discrete. 698 ResampleReal center = (cast(ResampleReal)i+NUDGE)/xscale; 699 center -= NUDGE; 700 center += srcOfs; 701 immutable int left = castToInt(cast(ResampleReal)floor(center-halfWidth)); 702 immutable int right = castToInt(cast(ResampleReal)ceil(center+halfWidth)); 703 PcontribBounds[i].center = center; 704 PcontribBounds[i].left = left; 705 PcontribBounds[i].right = right; 706 total += (right-left+1); 707 } 708 709 // Allocate memory for contributors. 710 if (total == 0 || ((Pcpool = cast(Contrib*)calloc(total, Contrib.sizeof)) is null)) return null; 711 //scope(failure) free(Pcpool); 712 713 PcpoolNext = Pcpool; 714 715 // Create the list of source samples which contribute to each destination sample. 716 foreach (immutable i; 0..dstX) { 717 int maxK = -1; 718 ResampleReal maxW = -1e+20f; 719 720 ResampleReal center = PcontribBounds[i].center; 721 immutable int left = PcontribBounds[i].left; 722 immutable int right = PcontribBounds[i].right; 723 724 Pcontrib[i].n = 0; 725 Pcontrib[i].p = PcpoolNext; 726 PcpoolNext += (right-left+1); 727 assert(PcpoolNext-Pcpool <= total); 728 729 ResampleReal totalWeight0 = 0; 730 foreach (immutable j; left..right+1) totalWeight0 += Pfilter((center-cast(ResampleReal)j)*ooFilterScale); 731 immutable ResampleReal norm = cast(ResampleReal)(1.0f/totalWeight0); 732 733 ResampleReal totalWeight1 = 0; 734 foreach (immutable j; left..right+1) { 735 immutable ResampleReal weight = Pfilter((center-cast(ResampleReal)j)*ooFilterScale)*norm; 736 if (weight == 0.0f) continue; 737 immutable int n = reflect(j, srcX, boundaryOp); 738 // Increment the number of source samples which contribute to the current destination sample. 739 immutable int k = Pcontrib[i].n++; 740 Pcontrib[i].p[k].pixel = cast(ushort)(n); // store src sample number 741 Pcontrib[i].p[k].weight = weight; // store src sample weight 742 totalWeight1 += weight; // total weight of all contributors 743 if (weight > maxW) { 744 maxW = weight; 745 maxK = k; 746 } 747 } 748 //assert(Pcontrib[i].n); 749 //assert(max_k != -1); 750 if (maxK == -1 || Pcontrib[i].n == 0) return null; 751 if (totalWeight1 != 1.0f) Pcontrib[i].p[maxK].weight += 1.0f-totalWeight1; 752 } 753 } 754 // don't free return value 755 PcontribRes = Pcontrib; 756 Pcontrib = null; 757 return PcontribRes; 758 } 759 760 static int countOps (const(ContribList)* Pclist, int k) { 761 int t = 0; 762 foreach (immutable i; 0..k) t += Pclist[i].n; 763 return t; 764 } 765 766 private ResampleReal mLo; 767 private ResampleReal mHi; 768 769 ResampleReal clampSample (ResampleReal f) const { 770 pragma(inline, true); 771 if (f < mLo) f = mLo; else if (f > mHi) f = mHi; 772 return f; 773 } 774 775 public: 776 // src_x/src_y - Input dimensions 777 // dst_x/dst_y - Output dimensions 778 // boundary_op - How to sample pixels near the image boundaries 779 // sample_low/sample_high - Clamp output samples to specified range, or disable clamping if sample_low >= sample_high 780 // Pclist_x/Pclist_y - Optional pointers to contributor lists from another instance of a ImageResampleWorker 781 // src_x_ofs/src_y_ofs - Offset input image by specified amount (fractional values okay) 782 this( 783 int srcX, int srcY, 784 int dstX, int dstY, 785 BoundaryOp boundaryOp=BoundaryClamp, 786 ResampleReal sampleLow=0.0f, ResampleReal sampleHigh=0.0f, 787 int PfilterIndex=-1, 788 ContribList* PclistX=null, 789 ContribList* PclistY=null, 790 ResampleReal filterXScale=1.0f, 791 ResampleReal filterYScale=1.0f, 792 ResampleReal srcXOfs=0.0f, 793 ResampleReal srcYOfs=0.0f) 794 { 795 import core.stdc.stdlib : calloc, malloc; 796 797 int i, j; 798 ResampleReal support; 799 FilterFunc func; 800 801 assert(srcX > 0); 802 assert(srcY > 0); 803 assert(dstX > 0); 804 assert(dstY > 0); 805 806 mLo = sampleLow; 807 mHi = sampleHigh; 808 809 mDelayXResample = false; 810 mIntermediateX = 0; 811 mPdstBuf = null; 812 mPtmpBuf = null; 813 mClistXForced = false; 814 mPclistX = null; 815 mClistYForced = false; 816 mPclistY = null; 817 mPsrcYCount = null; 818 mPsrcYFlag = null; 819 mPscanBuf = null; 820 mStatus = StatusOkay; 821 822 mResampleSrcX = srcX; 823 mResampleSrcY = srcY; 824 mResampleDstX = dstX; 825 mResampleDstY = dstY; 826 827 mBoundaryOp = boundaryOp; 828 829 if ((mPdstBuf = cast(Sample*)malloc(mResampleDstX*Sample.sizeof)) is null) { 830 mStatus = StatusOutOfMemory; 831 return; 832 } 833 834 if (PfilterIndex < 0 || PfilterIndex >= NumFilters) { 835 PfilterIndex = resamplerFindFilterInternal(ImageResizeDefaultFilter); 836 if (PfilterIndex < 0 || PfilterIndex >= NumFilters) { 837 mStatus = StatusBadFilterName; 838 return; 839 } 840 } 841 842 func = gFilters[PfilterIndex].func; 843 support = gFilters[PfilterIndex].support; 844 845 // Create contributor lists, unless the user supplied custom lists. 846 if (PclistX is null) { 847 mPclistX = makeClist(mResampleSrcX, mResampleDstX, mBoundaryOp, func, support, filterXScale, srcXOfs); 848 if (mPclistX is null) { 849 mStatus = StatusOutOfMemory; 850 return; 851 } 852 } else { 853 mPclistX = PclistX; 854 mClistXForced = true; 855 } 856 857 if (PclistY is null) { 858 mPclistY = makeClist(mResampleSrcY, mResampleDstY, mBoundaryOp, func, support, filterYScale, srcYOfs); 859 if (mPclistY is null) { 860 mStatus = StatusOutOfMemory; 861 return; 862 } 863 } else { 864 mPclistY = PclistY; 865 mClistYForced = true; 866 } 867 868 if ((mPsrcYCount = cast(int*)calloc(mResampleSrcY, int.sizeof)) is null) { 869 mStatus = StatusOutOfMemory; 870 return; 871 } 872 873 if ((mPsrcYFlag = cast(ubyte*)calloc(mResampleSrcY, ubyte.sizeof)) is null) { 874 mStatus = StatusOutOfMemory; 875 return; 876 } 877 878 // Count how many times each source line contributes to a destination line. 879 for (i = 0; i < mResampleDstY; ++i) { 880 for (j = 0; j < mPclistY[i].n; ++j) { 881 ++mPsrcYCount[resamplerRangeCheck(mPclistY[i].p[j].pixel, mResampleSrcY)]; 882 } 883 } 884 885 if ((mPscanBuf = cast(ScanBuf*)malloc(ScanBuf.sizeof)) is null) { 886 mStatus = StatusOutOfMemory; 887 return; 888 } 889 890 for (i = 0; i < MaxScanBufSize; ++i) { 891 mPscanBuf.scanBufY.ptr[i] = -1; 892 mPscanBuf.scanBufL.ptr[i] = null; 893 } 894 895 mCurSrcY = mCurDstY = 0; 896 { 897 // Determine which axis to resample first by comparing the number of multiplies required 898 // for each possibility. 899 int xOps = countOps(mPclistX, mResampleDstX); 900 int yOps = countOps(mPclistY, mResampleDstY); 901 902 // Hack 10/2000: Weight Y axis ops a little more than X axis ops. 903 // (Y axis ops use more cache resources.) 904 int xyOps = xOps*mResampleSrcY+(4*yOps*mResampleDstX)/3; 905 int yxOps = (4*yOps*mResampleSrcX)/3+xOps*mResampleDstY; 906 907 // Now check which resample order is better. In case of a tie, choose the order 908 // which buffers the least amount of data. 909 if (xyOps > yxOps || (xyOps == yxOps && mResampleSrcX < mResampleDstX)) { 910 mDelayXResample = true; 911 mIntermediateX = mResampleSrcX; 912 } else { 913 mDelayXResample = false; 914 mIntermediateX = mResampleDstX; 915 } 916 } 917 918 if (mDelayXResample) { 919 if ((mPtmpBuf = cast(Sample*)malloc(mIntermediateX*Sample.sizeof)) is null) { 920 mStatus = StatusOutOfMemory; 921 return; 922 } 923 } 924 } 925 926 ~this () { 927 import core.stdc.stdlib : free; 928 929 if (mPdstBuf !is null) { 930 free(mPdstBuf); 931 mPdstBuf = null; 932 } 933 934 if (mPtmpBuf !is null) { 935 free(mPtmpBuf); 936 mPtmpBuf = null; 937 } 938 939 // Don't deallocate a contibutor list if the user passed us one of their own. 940 if (mPclistX !is null && !mClistXForced) { 941 free(mPclistX.p); 942 free(mPclistX); 943 mPclistX = null; 944 } 945 if (mPclistY !is null && !mClistYForced) { 946 free(mPclistY.p); 947 free(mPclistY); 948 mPclistY = null; 949 } 950 951 if (mPsrcYCount !is null) { 952 free(mPsrcYCount); 953 mPsrcYCount = null; 954 } 955 956 if (mPsrcYFlag !is null) { 957 free(mPsrcYFlag); 958 mPsrcYFlag = null; 959 } 960 961 if (mPscanBuf !is null) { 962 foreach (immutable i; 0..MaxScanBufSize) if (mPscanBuf.scanBufL.ptr[i] !is null) free(mPscanBuf.scanBufL.ptr[i]); 963 free(mPscanBuf); 964 mPscanBuf = null; 965 } 966 } 967 968 // Reinits resampler so it can handle another frame. 969 void restart () { 970 import core.stdc.stdlib : free; 971 if (StatusOkay != mStatus) return; 972 mCurSrcY = mCurDstY = 0; 973 foreach (immutable i; 0..mResampleSrcY) { 974 mPsrcYCount[i] = 0; 975 mPsrcYFlag[i] = false; 976 } 977 foreach (immutable i; 0..mResampleDstY) { 978 foreach (immutable j; 0..mPclistY[i].n) { 979 ++mPsrcYCount[resamplerRangeCheck(mPclistY[i].p[j].pixel, mResampleSrcY)]; 980 } 981 } 982 foreach (immutable i; 0..MaxScanBufSize) { 983 mPscanBuf.scanBufY.ptr[i] = -1; 984 free(mPscanBuf.scanBufL.ptr[i]); 985 mPscanBuf.scanBufL.ptr[i] = null; 986 } 987 } 988 989 // false on out of memory. 990 bool putLine (const(Sample)* Psrc) { 991 int i; 992 993 if (mCurSrcY >= mResampleSrcY) return false; 994 995 // Does this source line contribute to any destination line? if not, exit now. 996 if (!mPsrcYCount[resamplerRangeCheck(mCurSrcY, mResampleSrcY)]) { 997 ++mCurSrcY; 998 return true; 999 } 1000 1001 // Find an empty slot in the scanline buffer. (FIXME: Perf. is terrible here with extreme scaling ratios.) 1002 for (i = 0; i < MaxScanBufSize; ++i) if (mPscanBuf.scanBufY.ptr[i] == -1) break; 1003 1004 // If the buffer is full, exit with an error. 1005 if (i == MaxScanBufSize) { 1006 mStatus = StatusScanBufferFull; 1007 return false; 1008 } 1009 1010 mPsrcYFlag[resamplerRangeCheck(mCurSrcY, mResampleSrcY)] = true; 1011 mPscanBuf.scanBufY.ptr[i] = mCurSrcY; 1012 1013 // Does this slot have any memory allocated to it? 1014 if (!mPscanBuf.scanBufL.ptr[i]) { 1015 import core.stdc.stdlib : malloc; 1016 if ((mPscanBuf.scanBufL.ptr[i] = cast(Sample*)malloc(mIntermediateX*Sample.sizeof)) is null) { 1017 mStatus = StatusOutOfMemory; 1018 return false; 1019 } 1020 } 1021 1022 // Resampling on the X axis first? 1023 if (mDelayXResample) { 1024 import core.stdc.string : memcpy; 1025 assert(mIntermediateX == mResampleSrcX); 1026 // Y-X resampling order 1027 memcpy(mPscanBuf.scanBufL.ptr[i], Psrc, mIntermediateX*Sample.sizeof); 1028 } else { 1029 assert(mIntermediateX == mResampleDstX); 1030 // X-Y resampling order 1031 resampleX(mPscanBuf.scanBufL.ptr[i], Psrc); 1032 } 1033 1034 ++mCurSrcY; 1035 1036 return true; 1037 } 1038 1039 // null if no scanlines are currently available (give the resampler more scanlines!) 1040 const(Sample)* getLine () { 1041 // if all the destination lines have been generated, then always return null 1042 if (mCurDstY == mResampleDstY) return null; 1043 // check to see if all the required contributors are present, if not, return null 1044 foreach (immutable i; 0..mPclistY[mCurDstY].n) { 1045 if (!mPsrcYFlag[resamplerRangeCheck(mPclistY[mCurDstY].p[i].pixel, mResampleSrcY)]) return null; 1046 } 1047 resampleY(mPdstBuf); 1048 ++mCurDstY; 1049 return mPdstBuf; 1050 } 1051 1052 @property Status status () const { pragma(inline, true); return mStatus; } 1053 1054 // returned contributor lists can be shared with another ImageResampleWorker 1055 void getClists (ContribList** ptrClistX, ContribList** ptrClistY) { 1056 if (ptrClistX !is null) *ptrClistX = mPclistX; 1057 if (ptrClistY !is null) *ptrClistY = mPclistY; 1058 } 1059 1060 @property ContribList* getClistX () { pragma(inline, true); return mPclistX; } 1061 @property ContribList* getClistY () { pragma(inline, true); return mPclistY; } 1062 1063 // filter accessors 1064 static @property auto filters () { 1065 static struct FilterRange { 1066 pure nothrow @trusted @nogc: 1067 int idx; 1068 @property bool empty () const { pragma(inline, true); return (idx >= NumFilters); } 1069 @property string front () const { pragma(inline, true); return (idx < NumFilters ? gFilters[idx].name : null); } 1070 void popFront () { if (idx < NumFilters) ++idx; } 1071 int length () const { return cast(int)NumFilters; } 1072 alias opDollar = length; 1073 } 1074 return FilterRange(); 1075 } 1076 1077 private: 1078 /* Ensure that the contributing source sample is 1079 * within bounds. If not, reflect, clamp, or wrap. 1080 */ 1081 int reflect (in int j, in int srcX, in BoundaryOp boundaryOp) { 1082 int n; 1083 if (j < 0) { 1084 if (boundaryOp == BoundaryReflect) { 1085 n = -j; 1086 if (n >= srcX) n = srcX-1; 1087 } else if (boundaryOp == BoundaryWrap) { 1088 n = posmod(j, srcX); 1089 } else { 1090 n = 0; 1091 } 1092 } else if (j >= srcX) { 1093 if (boundaryOp == BoundaryReflect) { 1094 n = (srcX-j)+(srcX-1); 1095 if (n < 0) n = 0; 1096 } else if (boundaryOp == BoundaryWrap) { 1097 n = posmod(j, srcX); 1098 } else { 1099 n = srcX-1; 1100 } 1101 } else { 1102 n = j; 1103 } 1104 return n; 1105 } 1106 1107 void resampleX (Sample* Pdst, const(Sample)* Psrc) { 1108 assert(Pdst); 1109 assert(Psrc); 1110 1111 Sample total; 1112 ContribList *Pclist = mPclistX; 1113 Contrib *p; 1114 1115 for (int i = mResampleDstX; i > 0; --i, ++Pclist) { 1116 int j = void; 1117 for (j = Pclist.n, p = Pclist.p, total = 0; j > 0; --j, ++p) total += Psrc[p.pixel]*p.weight; 1118 *Pdst++ = total; 1119 } 1120 } 1121 1122 void scaleYMov (Sample* Ptmp, const(Sample)* Psrc, ResampleReal weight, int dstX) { 1123 // Not += because temp buf wasn't cleared. 1124 for (int i = dstX; i > 0; --i) *Ptmp++ = *Psrc++*weight; 1125 } 1126 1127 void scaleYAdd (Sample* Ptmp, const(Sample)* Psrc, ResampleReal weight, int dstX) { 1128 for (int i = dstX; i > 0; --i) (*Ptmp++) += *Psrc++*weight; 1129 } 1130 1131 void clamp (Sample* Pdst, int n) { 1132 while (n > 0) { 1133 *Pdst = clampSample(*Pdst); 1134 ++Pdst; 1135 --n; 1136 } 1137 } 1138 1139 void resampleY (Sample* Pdst) { 1140 Sample* Psrc; 1141 ContribList* Pclist = &mPclistY[mCurDstY]; 1142 1143 Sample* Ptmp = mDelayXResample ? mPtmpBuf : Pdst; 1144 assert(Ptmp); 1145 1146 // process each contributor 1147 foreach (immutable i; 0..Pclist.n) { 1148 // locate the contributor's location in the scan buffer -- the contributor must always be found! 1149 int j = void; 1150 for (j = 0; j < MaxScanBufSize; ++j) if (mPscanBuf.scanBufY.ptr[j] == Pclist.p[i].pixel) break; 1151 assert(j < MaxScanBufSize); 1152 Psrc = mPscanBuf.scanBufL.ptr[j]; 1153 if (!i) { 1154 scaleYMov(Ptmp, Psrc, Pclist.p[i].weight, mIntermediateX); 1155 } else { 1156 scaleYAdd(Ptmp, Psrc, Pclist.p[i].weight, mIntermediateX); 1157 } 1158 1159 /* If this source line doesn't contribute to any 1160 * more destination lines then mark the scanline buffer slot 1161 * which holds this source line as free. 1162 * (The max. number of slots used depends on the Y 1163 * axis sampling factor and the scaled filter width.) 1164 */ 1165 1166 if (--mPsrcYCount[resamplerRangeCheck(Pclist.p[i].pixel, mResampleSrcY)] == 0) { 1167 mPsrcYFlag[resamplerRangeCheck(Pclist.p[i].pixel, mResampleSrcY)] = false; 1168 mPscanBuf.scanBufY.ptr[j] = -1; 1169 } 1170 } 1171 1172 // now generate the destination line 1173 if (mDelayXResample) { 1174 // X was resampling delayed until after Y resampling 1175 assert(Pdst != Ptmp); 1176 resampleX(Pdst, Ptmp); 1177 } else { 1178 assert(Pdst == Ptmp); 1179 } 1180 1181 if (mLo < mHi) clamp(Pdst, mResampleDstX); 1182 } 1183 } 1184 1185 1186 // ////////////////////////////////////////////////////////////////////////// // 1187 private nothrow @trusted @nogc: 1188 int resamplerRangeCheck (int v, int h) { 1189 version(assert) { 1190 //import std.conv : to; 1191 //assert(v >= 0 && v < h, "invalid v ("~to!string(v)~"), should be in [0.."~to!string(h)~")"); 1192 assert(v >= 0 && v < h); // alas, @nogc 1193 return v; 1194 } else { 1195 pragma(inline, true); 1196 return v; 1197 } 1198 } 1199 1200 enum M_PI = 3.14159265358979323846; 1201 1202 // Float to int cast with truncation. 1203 int castToInt (ImageResampleWorker.ResampleReal i) { pragma(inline, true); return cast(int)i; } 1204 1205 // (x mod y) with special handling for negative x values. 1206 int posmod (int x, int y) { 1207 pragma(inline, true); 1208 if (x >= 0) { 1209 return (x%y); 1210 } else { 1211 int m = (-x)%y; 1212 if (m != 0) m = y-m; 1213 return m; 1214 } 1215 } 1216 1217 // To add your own filter, insert the new function below and update the filter table. 1218 // There is no need to make the filter function particularly fast, because it's 1219 // only called during initializing to create the X and Y axis contributor tables. 1220 1221 /* pulse/Fourier window */ 1222 enum BoxFilterSupport = 0.5f; 1223 ImageResampleWorker.ResampleReal boxFilter (ImageResampleWorker.ResampleReal t) { 1224 // make_clist() calls the filter function with t inverted (pos = left, neg = right) 1225 if (t >= -0.5f && t < 0.5f) return 1.0f; else return 0.0f; 1226 } 1227 1228 /* box (*) box, bilinear/triangle */ 1229 enum TentFilterSupport = 1.0f; 1230 ImageResampleWorker.ResampleReal tentFilter (ImageResampleWorker.ResampleReal t) { 1231 if (t < 0.0f) t = -t; 1232 if (t < 1.0f) return 1.0f-t; else return 0.0f; 1233 } 1234 1235 /* box (*) box (*) box */ 1236 enum BellSupport = 1.5f; 1237 ImageResampleWorker.ResampleReal bellFilter (ImageResampleWorker.ResampleReal t) { 1238 if (t < 0.0f) t = -t; 1239 if (t < 0.5f) return (0.75f-(t*t)); 1240 if (t < 1.5f) { t = (t-1.5f); return (0.5f*(t*t)); } 1241 return (0.0f); 1242 } 1243 1244 /* box (*) box (*) box (*) box */ 1245 enum BSplineSupport = 2.0f; 1246 ImageResampleWorker.ResampleReal BSplineFilter (ImageResampleWorker.ResampleReal t) { 1247 if (t < 0.0f) t = -t; 1248 if (t < 1.0f) { immutable ImageResampleWorker.ResampleReal tt = t*t; return ((0.5f*tt*t)-tt+(2.0f/3.0f)); } 1249 if (t < 2.0f) { t = 2.0f-t; return ((1.0f/6.0f)*(t*t*t)); } 1250 return 0.0f; 1251 } 1252 1253 // Dodgson, N., "Quadratic Interpolation for Image Resampling" 1254 enum QuadraticSupport = 1.5f; 1255 ImageResampleWorker.ResampleReal quadratic (ImageResampleWorker.ResampleReal t, in ImageResampleWorker.ResampleReal R) { 1256 pragma(inline, true); 1257 if (t < 0.0f) t = -t; 1258 if (t < QuadraticSupport) { 1259 immutable ImageResampleWorker.ResampleReal tt = t*t; 1260 if (t <= 0.5f) return (-2.0f*R)*tt+0.5f*(R+1.0f); 1261 return (R*tt)+(-2.0f*R-0.5f)*t+(3.0f/4.0f)*(R+1.0f); 1262 } 1263 return 0.0f; 1264 } 1265 1266 ImageResampleWorker.ResampleReal quadraticInterpFilter (ImageResampleWorker.ResampleReal t) { 1267 return quadratic(t, 1.0f); 1268 } 1269 1270 ImageResampleWorker.ResampleReal quadraticApproxFilter (ImageResampleWorker.ResampleReal t) { 1271 return quadratic(t, 0.5f); 1272 } 1273 1274 ImageResampleWorker.ResampleReal quadraticMixFilter (ImageResampleWorker.ResampleReal t) { 1275 return quadratic(t, 0.8f); 1276 } 1277 1278 // Mitchell, D. and A. Netravali, "Reconstruction Filters in Computer Graphics." 1279 // Computer Graphics, Vol. 22, No. 4, pp. 221-228. 1280 // (B, C) 1281 // (1/3, 1/3) - Defaults recommended by Mitchell and Netravali 1282 // (1, 0) - Equivalent to the Cubic B-Spline 1283 // (0, 0.5) - Equivalent to the Catmull-Rom Spline 1284 // (0, C) - The family of Cardinal Cubic Splines 1285 // (B, 0) - Duff's tensioned B-Splines. 1286 ImageResampleWorker.ResampleReal mitchell (ImageResampleWorker.ResampleReal t, in ImageResampleWorker.ResampleReal B, in ImageResampleWorker.ResampleReal C) { 1287 ImageResampleWorker.ResampleReal tt = t*t; 1288 if (t < 0.0f) t = -t; 1289 if (t < 1.0f) { 1290 t = (((12.0f-9.0f*B-6.0f*C)*(t*tt))+ 1291 ((-18.0f+12.0f*B+6.0f*C)*tt)+ 1292 (6.0f-2.0f*B)); 1293 return (t/6.0f); 1294 } 1295 if (t < 2.0f) { 1296 t = (((-1.0f*B-6.0f*C)*(t*tt))+ 1297 ((6.0f*B+30.0f*C)*tt)+ 1298 ((-12.0f*B-48.0f*C)*t)+ 1299 (8.0f*B+24.0f*C)); 1300 return (t/6.0f); 1301 } 1302 return 0.0f; 1303 } 1304 1305 enum MitchellSupport = 2.0f; 1306 ImageResampleWorker.ResampleReal mitchellFilter (ImageResampleWorker.ResampleReal t) { 1307 return mitchell(t, 1.0f/3.0f, 1.0f/3.0f); 1308 } 1309 1310 enum CatmullRomSupport = 2.0f; 1311 ImageResampleWorker.ResampleReal catmullRomFilter (ImageResampleWorker.ResampleReal t) { 1312 return mitchell(t, 0.0f, 0.5f); 1313 } 1314 1315 double sinc (double x) { 1316 pragma(inline, true); 1317 import std.math : sin; 1318 x *= M_PI; 1319 if (x < 0.01f && x > -0.01f) return 1.0f+x*x*(-1.0f/6.0f+x*x*1.0f/120.0f); 1320 return sin(x)/x; 1321 } 1322 1323 ImageResampleWorker.ResampleReal clean (double t) { 1324 pragma(inline, true); 1325 import std.math : abs; 1326 enum EPSILON = cast(ImageResampleWorker.ResampleReal)0.0000125f; 1327 if (abs(t) < EPSILON) return 0.0f; 1328 return cast(ImageResampleWorker.ResampleReal)t; 1329 } 1330 1331 //static double blackman_window(double x) 1332 //{ 1333 // return 0.42f+0.50f*cos(M_PI*x)+0.08f*cos(2.0f*M_PI*x); 1334 //} 1335 1336 double blackmanExactWindow (double x) { 1337 pragma(inline, true); 1338 import std.math : cos; 1339 return 0.42659071f+0.49656062f*cos(M_PI*x)+0.07684867f*cos(2.0f*M_PI*x); 1340 } 1341 1342 enum BlackmanSupport = 3.0f; 1343 ImageResampleWorker.ResampleReal blackmanFilter (ImageResampleWorker.ResampleReal t) { 1344 if (t < 0.0f) t = -t; 1345 if (t < 3.0f) { 1346 //return clean(sinc(t)*blackman_window(t/3.0f)); 1347 return clean(sinc(t)*blackmanExactWindow(t/3.0f)); 1348 } 1349 return (0.0f); 1350 } 1351 1352 // with blackman window 1353 enum GaussianSupport = 1.25f; 1354 ImageResampleWorker.ResampleReal gaussianFilter (ImageResampleWorker.ResampleReal t) { 1355 import std.math : exp, sqrt; 1356 if (t < 0) t = -t; 1357 if (t < GaussianSupport) return clean(exp(-2.0f*t*t)*sqrt(2.0f/M_PI)*blackmanExactWindow(t/GaussianSupport)); 1358 return 0.0f; 1359 } 1360 1361 // Windowed sinc -- see "Jimm Blinn's Corner: Dirty Pixels" pg. 26. 1362 enum Lanczos3Support = 3.0f; 1363 ImageResampleWorker.ResampleReal lanczos3Filter (ImageResampleWorker.ResampleReal t) { 1364 if (t < 0.0f) t = -t; 1365 if (t < 3.0f) return clean(sinc(t)*sinc(t/3.0f)); 1366 return (0.0f); 1367 } 1368 1369 enum Lanczos4Support = 4.0f; 1370 ImageResampleWorker.ResampleReal lanczos4Filter (ImageResampleWorker.ResampleReal t) { 1371 if (t < 0.0f) t = -t; 1372 if (t < 4.0f) return clean(sinc(t)*sinc(t/4.0f)); 1373 return (0.0f); 1374 } 1375 1376 enum Lanczos6Support = 6.0f; 1377 ImageResampleWorker.ResampleReal lanczos6Filter (ImageResampleWorker.ResampleReal t) { 1378 if (t < 0.0f) t = -t; 1379 if (t < 6.0f) return clean(sinc(t)*sinc(t/6.0f)); 1380 return (0.0f); 1381 } 1382 1383 enum Lanczos12Support = 12.0f; 1384 ImageResampleWorker.ResampleReal lanczos12Filter (ImageResampleWorker.ResampleReal t) { 1385 if (t < 0.0f) t = -t; 1386 if (t < 12.0f) return clean(sinc(t)*sinc(t/12.0f)); 1387 return (0.0f); 1388 } 1389 1390 double bessel0 (double x) { 1391 enum EpsilonRatio = cast(double)1E-16; 1392 double xh = 0.5*x; 1393 double sum = 1.0; 1394 double pow = 1.0; 1395 int k = 0; 1396 double ds = 1.0; 1397 // FIXME: Shouldn't this stop after X iterations for max. safety? 1398 while (ds > sum*EpsilonRatio) { 1399 ++k; 1400 pow = pow*(xh/k); 1401 ds = pow*pow; 1402 sum = sum+ds; 1403 } 1404 return sum; 1405 } 1406 1407 enum KaiserAlpha = cast(ImageResampleWorker.ResampleReal)4.0; 1408 double kaiser (double alpha, double halfWidth, double x) { 1409 pragma(inline, true); 1410 import std.math : sqrt; 1411 immutable double ratio = (x/halfWidth); 1412 return bessel0(alpha*sqrt(1-ratio*ratio))/bessel0(alpha); 1413 } 1414 1415 enum KaiserSupport = 3; 1416 static ImageResampleWorker.ResampleReal kaiserFilter (ImageResampleWorker.ResampleReal t) { 1417 if (t < 0.0f) t = -t; 1418 if (t < KaiserSupport) { 1419 import std.math : exp, log; 1420 // db atten 1421 immutable ImageResampleWorker.ResampleReal att = 40.0f; 1422 immutable ImageResampleWorker.ResampleReal alpha = cast(ImageResampleWorker.ResampleReal)(exp(log(cast(double)0.58417*(att-20.96))*0.4)+0.07886*(att-20.96)); 1423 //const ImageResampleWorker.Resample_Real alpha = KAISER_ALPHA; 1424 return cast(ImageResampleWorker.ResampleReal)clean(sinc(t)*kaiser(alpha, KaiserSupport, t)); 1425 } 1426 return 0.0f; 1427 } 1428 1429 // filters[] is a list of all the available filter functions. 1430 struct FilterInfo { 1431 string name; 1432 ImageResampleWorker.FilterFunc func; 1433 ImageResampleWorker.ResampleReal support; 1434 } 1435 1436 static immutable FilterInfo[16] gFilters = [ 1437 FilterInfo("box", &boxFilter, BoxFilterSupport), 1438 FilterInfo("tent", &tentFilter, TentFilterSupport), 1439 FilterInfo("bell", &bellFilter, BellSupport), 1440 FilterInfo("bspline", &BSplineFilter, BSplineSupport), 1441 FilterInfo("mitchell", &mitchellFilter, MitchellSupport), 1442 FilterInfo("lanczos3", &lanczos3Filter, Lanczos3Support), 1443 FilterInfo("blackman", &blackmanFilter, BlackmanSupport), 1444 FilterInfo("lanczos4", &lanczos4Filter, Lanczos4Support), 1445 FilterInfo("lanczos6", &lanczos6Filter, Lanczos6Support), 1446 FilterInfo("lanczos12", &lanczos12Filter, Lanczos12Support), 1447 FilterInfo("kaiser", &kaiserFilter, KaiserSupport), 1448 FilterInfo("gaussian", &gaussianFilter, GaussianSupport), 1449 FilterInfo("catmullrom", &catmullRomFilter, CatmullRomSupport), 1450 FilterInfo("quadratic_interp", &quadraticInterpFilter, QuadraticSupport), 1451 FilterInfo("quadratic_approx", &quadraticApproxFilter, QuadraticSupport), 1452 FilterInfo("quadratic_mix", &quadraticMixFilter, QuadraticSupport), 1453 ]; 1454 1455 enum NumFilters = cast(int)gFilters.length; 1456 1457 1458 bool rsmStringEqu (const(char)[] s0, const(char)[] s1) { 1459 for (;;) { 1460 if (s0.length && (s0.ptr[0] <= ' ' || s0.ptr[0] == '_')) { s0 = s0[1..$]; continue; } 1461 if (s1.length && (s1.ptr[0] <= ' ' || s1.ptr[0] == '_')) { s1 = s1[1..$]; continue; } 1462 if (s0.length == 0) { 1463 while (s1.length && (s1.ptr[0] <= ' ' || s1.ptr[0] == '_')) s1 = s1[1..$]; 1464 return (s1.length == 0); 1465 } 1466 if (s1.length == 0) { 1467 while (s0.length && (s0.ptr[0] <= ' ' || s0.ptr[0] == '_')) s0 = s0[1..$]; 1468 return (s0.length == 0); 1469 } 1470 assert(s0.length && s1.length); 1471 char c0 = s0.ptr[0]; 1472 char c1 = s1.ptr[0]; 1473 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 1474 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 1475 if (c0 != c1) return false; 1476 s0 = s0[1..$]; 1477 s1 = s1[1..$]; 1478 } 1479 } 1480 1481 1482 int resamplerFindFilterInternal (const(char)[] name) { 1483 if (name.length) { 1484 foreach (immutable idx, const ref fi; gFilters[]) if (rsmStringEqu(name, fi.name)) return cast(int)idx; 1485 } 1486 return -1; 1487 }