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 }