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