egra: better, faster, and more flexible vertical gradients in agg mini
[iv.d.git] / iresample.d
blobecaa5c6883bada3ef73bf38576772cf84e68c567
1 // Separable filtering image rescaler v2.21, Rich Geldreich - richgel99@gmail.com
2 //
3 // This is free and unencumbered software released into the public domain.
4 //
5 // Anyone is free to copy, modify, publish, use, compile, sell, or
6 // distribute this software, either in source code form or as a compiled
7 // binary, for any purpose, commercial or non-commercial, and by any
8 // means.
9 //
10 // In jurisdictions that recognize copyright laws, the author or authors
11 // of this software dedicate any and all copyright interest in the
12 // software to the public domain. We make this dedication for the benefit
13 // of the public at large and to the detriment of our heirs and
14 // successors. We intend this dedication to be an overt act of
15 // relinquishment in perpetuity of all present and future rights to this
16 // software under copyright law.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
22 // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
23 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 // OTHER DEALINGS IN THE SOFTWARE.
26 // For more information, please refer to <http://unlicense.org/>
28 // Feb. 1996: Creation, losely based on a heavily bugfixed version of Schumacher's resampler in Graphics Gems 3.
29 // Oct. 2000: Ported to C++, tweaks.
30 // May 2001: Continous to discrete mapping, box filter tweaks.
31 // March 9, 2002: Kaiser filter grabbed from Jonathan Blow's GD magazine mipmap sample code.
32 // Sept. 8, 2002: Comments cleaned up a bit.
33 // Dec. 31, 2008: v2.2: Bit more cleanup, released as public domain.
34 // 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,
35 // added Codeblocks project (for testing with MinGW and GCC), VS2008 static code analysis pass.
36 // float or double
37 module iv.iresample /*is aliced*/;
38 private:
40 import arsd.color;
41 import iv.alice;
42 import iv.bclamp;
44 version = iresample_debug;
47 // ////////////////////////////////////////////////////////////////////////// //
48 public enum ResamplerDefaultFilter = "lanczos4";
49 public enum ResamplerMaxDimension = 65536;
52 // ////////////////////////////////////////////////////////////////////////// //
53 public @property int resamplerFilterCount () { pragma(inline, true); return NumFilters; }
54 public string resamplerFilterName (long idx) { pragma(inline, true); return (idx >= 0 && idx < NumFilters ? gFilters.ptr[cast(uint)idx].name : null); }
56 public int resamplerFindFilter (const(char)[] name, const(char)[] defaultFilter=ResamplerDefaultFilter) {
57 int res = resamplerFindFilterInternal(name);
58 if (res >= 0) return res;
59 res = resamplerFindFilterInternal(defaultFilter);
60 if (res >= 0) return res;
61 res = resamplerFindFilterInternal("lanczos4");
62 assert(res >= 0);
63 return res;
67 // ////////////////////////////////////////////////////////////////////////// //
68 public TrueColorImage imageResample(int Components=4) (MemoryImage msrcimg, int dstwdt, int dsthgt, const(char)[] filter=null, float gamma=1.0f, float filterScale=1.0f) {
69 static assert(Components == 1 || Components == 3 || Components == 4, "invalid number of components in color");
70 return imageResample!Components(msrcimg, dstwdt, dsthgt, resamplerFindFilter(filter), gamma, filterScale);
73 public TrueColorImage imageResample(int Components=4) (MemoryImage msrcimg, int dstwdt, int dsthgt, int filter, float gamma=1.0f, float filterScale=1.0f) {
74 static assert(Components == 1 || Components == 3 || Components == 4, "invalid number of components in color");
75 if (msrcimg is null || msrcimg.width < 1 || msrcimg.height < 1 || msrcimg.width > ResamplerMaxDimension || msrcimg.height > ResamplerMaxDimension) {
76 throw new Exception("invalid source image");
78 if (dstwdt < 1 || dsthgt < 1 || dstwdt > ResamplerMaxDimension || dsthgt > ResamplerMaxDimension) throw new Exception("invalid destination image size");
79 auto resimg = new TrueColorImage(dstwdt, dsthgt);
80 scope(failure) delete resimg;
81 if (auto tc = cast(TrueColorImage)msrcimg) {
82 imageResample!Components(
83 delegate (Color[] destrow, int y) { destrow[] = tc.imageData.colors[y*tc.width..(y+1)*tc.width]; },
84 delegate (int y, const(Color)[] row) { resimg.imageData.colors[y*resimg.width..(y+1)*resimg.width] = row[]; },
85 msrcimg.width, msrcimg.height, dstwdt, dsthgt, filter, gamma, filterScale
87 } else {
88 imageResample!Components(
89 delegate (Color[] destrow, int y) { foreach (immutable x, ref c; destrow) c = msrcimg.getPixel(cast(int)x, y); },
90 delegate (int y, const(Color)[] row) { resimg.imageData.colors[y*resimg.width..(y+1)*resimg.width] = row[]; },
91 msrcimg.width, msrcimg.height, dstwdt, dsthgt, filter, gamma, filterScale
94 return resimg;
98 private {
99 enum Linear2srgbTableSize = 4096;
100 enum InvLinear2srgbTableSize = cast(float)(1.0f/Linear2srgbTableSize);
101 float[256] srgb2linear = void;
102 ubyte[Linear2srgbTableSize] linear2srgb = void;
103 float lastGamma = float.nan;
106 // partial gamma correction looks better on mips; set to 1.0 to disable gamma correction
107 // filter scale: values < 1.0 cause aliasing, but create sharper looking mips (0.75f, for example)
108 public void imageResample(int Components=4) (
109 scope void delegate (Color[] destrow, int y) srcGetRow,
110 scope void delegate (int y, const(Color)[] row) dstPutRow,
111 int srcwdt, int srchgt, int dstwdt, int dsthgt,
112 int filter=-1, float gamma=1.0f, float filterScale=1.0f
114 static assert(Components == 1 || Components == 3 || Components == 4, "invalid number of components in color");
115 assert(srcGetRow !is null);
116 assert(dstPutRow !is null);
118 if (srcwdt < 1 || srchgt < 1 || dstwdt < 1 || dsthgt < 1 ||
119 srcwdt > ResamplerMaxDimension || srchgt > ResamplerMaxDimension ||
120 dstwdt > ResamplerMaxDimension || dsthgt > ResamplerMaxDimension) throw new Exception("invalid image size");
122 if (filter < 0 || filter >= NumFilters) {
123 filter = resamplerFindFilterInternal(ResamplerDefaultFilter);
124 if (filter < 0) {
125 filter = resamplerFindFilterInternal("lanczos4");
128 assert(filter >= 0 && filter < NumFilters);
131 if (lastGamma != gamma) {
132 version(iresample_debug) { import core.stdc.stdio; stderr.fprintf("creating translation tables for gamma %f (previous gamma is %f)\n", gamma, lastGamma); }
133 foreach (immutable i, ref v; srgb2linear[]) {
134 import std.math : pow;
135 v = cast(float)pow(cast(int)i*1.0f/255.0f, gamma);
137 immutable float invSourceGamma = 1.0f/gamma;
138 foreach (immutable i, ref v; linear2srgb[]) {
139 import std.math : pow;
140 int k = cast(int)(255.0f*pow(cast(int)i*InvLinear2srgbTableSize, invSourceGamma)+0.5f);
141 if (k < 0) k = 0; else if (k > 255) k = 255;
142 v = cast(ubyte)k;
144 lastGamma = gamma;
146 version(iresample_debug) { import core.stdc.stdio; stderr.fprintf("filter is %d\n", filter); }
148 Resampler[Components] resamplers;
149 float[][Components] samples;
150 Color[] srcrow, dstrow;
151 scope(exit) {
152 foreach (ref rsm; resamplers[]) delete rsm;
153 foreach (ref smr; samples[]) delete smr;
154 delete srcrow;
155 delete dstrow;
158 // now create a Resampler instance for each component to process
159 // the first instance will create new contributor tables, which are shared by the resamplers
160 // used for the other components (a memory and slight cache efficiency optimization).
161 resamplers[0] = new Resampler(srcwdt, srchgt, dstwdt, dsthgt, Resampler.BoundaryClamp, 0.0f, 1.0f, filter, null, null, filterScale, filterScale);
162 samples[0].length = srcwdt;
163 srcrow.length = srcwdt;
164 dstrow.length = dstwdt;
165 foreach (immutable i; 1..Components) {
166 resamplers[i] = new Resampler(srcwdt, srchgt, dstwdt, dsthgt, Resampler.BoundaryClamp, 0.0f, 1.0f, filter, resamplers[0].getClistX(), resamplers[0].getClistY(), filterScale, filterScale);
167 samples[i].length = srcwdt;
170 int dsty = 0;
171 foreach (immutable int srcy; 0..srchgt) {
172 // get row components
173 srcGetRow(srcrow, srcy);
175 auto scp = srcrow.ptr;
176 foreach (immutable x; 0..srcwdt) {
177 auto sc = *scp++;
178 samples.ptr[0].ptr[x] = srgb2linear.ptr[sc.r]; // first component
179 static if (Components > 1) samples.ptr[1].ptr[x] = srgb2linear.ptr[sc.g]; // second component
180 static if (Components > 2) samples.ptr[2].ptr[x] = srgb2linear.ptr[sc.b]; // thirs component
181 static if (Components == 4) samples.ptr[3].ptr[x] = sc.a*(1.0f/255.0f); // fourth component is alpha, and it is already linear
185 foreach (immutable c; 0..Components) if (!resamplers.ptr[c].putLine(samples.ptr[c].ptr)) assert(0, "out of memory");
187 for (;;) {
188 int compIdx = 0;
189 for (; compIdx < Components; ++compIdx) {
190 const(float)* outsmp = resamplers.ptr[compIdx].getLine();
191 if (outsmp is null) break;
192 auto dsc = dstrow.ptr;
193 // alpha?
194 static if (Components == 4) {
195 if (compIdx == 3) {
196 foreach (immutable x; 0..dstwdt) {
197 dsc.a = clampToByte(cast(int)(255.0f*(*outsmp++)+0.5f));
198 ++dsc;
200 continue;
203 // color
204 auto dsb = (cast(ubyte*)dsc)+compIdx;
205 foreach (immutable x; 0..dstwdt) {
206 int j = cast(int)(Linear2srgbTableSize*(*outsmp++)+0.5f);
207 if (j < 0) j = 0; else if (j >= Linear2srgbTableSize) j = Linear2srgbTableSize-1;
208 *dsb = linear2srgb.ptr[j];
209 dsb += 4;
212 if (compIdx < Components) break;
213 // fill destination line
214 assert(dsty < dsthgt);
215 static if (Components != 4) {
216 auto dsc = dstrow.ptr;
217 foreach (immutable x; 0..dstwdt) {
218 static if (Components == 1) dsc.g = dsc.b = dsc.r;
219 dsc.a = 255;
220 ++dsc;
223 //version(iresample_debug) { import core.stdc.stdio; stderr.fprintf("writing dest row %d with %u components\n", dsty, Components); }
224 dstPutRow(dsty, dstrow);
225 ++dsty;
231 // ////////////////////////////////////////////////////////////////////////// //
232 public final class Resampler {
233 nothrow @trusted @nogc:
234 public:
235 alias ResampleReal = float;
236 alias Sample = ResampleReal;
238 static struct Contrib {
239 ResampleReal weight;
240 ushort pixel;
243 static struct ContribList {
244 ushort n;
245 Contrib* p;
248 alias BoundaryOp = int;
249 enum /*Boundary_Op*/ {
250 BoundaryWrap = 0,
251 BoundaryReflect = 1,
252 BoundaryClamp = 2,
255 alias Status = int;
256 enum /*Status*/ {
257 StatusOkay = 0,
258 StatusOutOfMemory = 1,
259 StatusBadFilterName = 2,
260 StatusScanBufferFull = 3,
263 private:
264 alias FilterFunc = ResampleReal function (ResampleReal) nothrow @trusted @nogc;
266 int mIntermediateX;
268 int mResampleSrcX;
269 int mResampleSrcY;
270 int mResampleDstX;
271 int mResampleDstY;
273 BoundaryOp mBoundaryOp;
275 Sample* mPdstBuf;
276 Sample* mPtmpBuf;
278 ContribList* mPclistX;
279 ContribList* mPclistY;
281 bool mClistXForced;
282 bool mClistYForced;
284 bool mDelayXResample;
286 int* mPsrcYCount;
287 ubyte* mPsrcYFlag;
289 // The maximum number of scanlines that can be buffered at one time.
290 enum MaxScanBufSize = ResamplerMaxDimension;
292 static struct ScanBuf {
293 int[MaxScanBufSize] scanBufY;
294 Sample*[MaxScanBufSize] scanBufL;
297 ScanBuf* mPscanBuf;
299 int mCurSrcY;
300 int mCurDstY;
302 Status mStatus;
304 // The make_clist() method generates, for all destination samples,
305 // the list of all source samples with non-zero weighted contributions.
306 ContribList* makeClist(
307 int srcX, int dstX, BoundaryOp boundaryOp,
308 FilterFunc Pfilter,
309 ResampleReal filterSupport,
310 ResampleReal filterScale,
311 ResampleReal srcOfs)
313 import core.stdc.stdlib : calloc, free;
314 import std.math : floor, ceil;
316 static struct ContribBounds {
317 // The center of the range in DISCRETE coordinates (pixel center = 0.0f).
318 ResampleReal center;
319 int left, right;
322 ContribList* Pcontrib, PcontribRes;
323 Contrib* Pcpool;
324 Contrib* PcpoolNext;
325 ContribBounds* PcontribBounds;
327 if ((Pcontrib = cast(ContribList*)calloc(dstX, ContribList.sizeof)) is null) return null;
328 scope(exit) if (Pcontrib !is null) free(Pcontrib);
330 PcontribBounds = cast(ContribBounds*)calloc(dstX, ContribBounds.sizeof);
331 if (PcontribBounds is null) return null;
332 scope(exit) free(PcontribBounds);
334 enum ResampleReal NUDGE = 0.5f;
335 immutable ResampleReal ooFilterScale = 1.0f/filterScale;
336 immutable ResampleReal xscale = dstX/cast(ResampleReal)srcX;
338 if (xscale < 1.0f) {
339 int total = 0;
340 // Handle case when there are fewer destination samples than source samples (downsampling/minification).
341 // stretched half width of filter
342 immutable ResampleReal halfWidth = (filterSupport/xscale)*filterScale;
343 // Find the range of source sample(s) that will contribute to each destination sample.
344 foreach (immutable i; 0..dstX) {
345 // Convert from discrete to continuous coordinates, scale, then convert back to discrete.
346 ResampleReal center = (cast(ResampleReal)i+NUDGE)/xscale;
347 center -= NUDGE;
348 center += srcOfs;
349 immutable int left = castToInt(cast(ResampleReal)floor(center-halfWidth));
350 immutable int right = castToInt(cast(ResampleReal)ceil(center+halfWidth));
351 PcontribBounds[i].center = center;
352 PcontribBounds[i].left = left;
353 PcontribBounds[i].right = right;
354 total += (right-left+1);
357 // Allocate memory for contributors.
358 if (total == 0 || ((Pcpool = cast(Contrib*)calloc(total, Contrib.sizeof)) is null)) return null;
359 //scope(failure) free(Pcpool);
360 //immutable int total = n;
362 PcpoolNext = Pcpool;
364 // Create the list of source samples which contribute to each destination sample.
365 foreach (immutable i; 0..dstX) {
366 int maxK = -1;
367 ResampleReal maxW = -1e+20f;
369 ResampleReal center = PcontribBounds[i].center;
370 immutable int left = PcontribBounds[i].left;
371 immutable int right = PcontribBounds[i].right;
373 Pcontrib[i].n = 0;
374 Pcontrib[i].p = PcpoolNext;
375 PcpoolNext += (right-left+1);
376 assert(PcpoolNext-Pcpool <= total);
378 ResampleReal totalWeight0 = 0;
379 foreach (immutable j; left..right+1) totalWeight0 += Pfilter((center-cast(ResampleReal)j)*xscale*ooFilterScale);
380 immutable ResampleReal norm = cast(ResampleReal)(1.0f/totalWeight0);
382 ResampleReal totalWeight1 = 0;
383 foreach (immutable j; left..right+1) {
384 immutable ResampleReal weight = Pfilter((center-cast(ResampleReal)j)*xscale*ooFilterScale)*norm;
385 if (weight == 0.0f) continue;
386 immutable int n = reflect(j, srcX, boundaryOp);
387 // Increment the number of source samples which contribute to the current destination sample.
388 immutable int k = Pcontrib[i].n++;
389 Pcontrib[i].p[k].pixel = cast(ushort)(n); // store src sample number
390 Pcontrib[i].p[k].weight = weight; // store src sample weight
391 totalWeight1 += weight; // total weight of all contributors
392 if (weight > maxW) {
393 maxW = weight;
394 maxK = k;
397 //assert(Pcontrib[i].n);
398 //assert(max_k != -1);
399 if (maxK == -1 || Pcontrib[i].n == 0) return null;
400 if (totalWeight1 != 1.0f) Pcontrib[i].p[maxK].weight += 1.0f-totalWeight1;
402 } else {
403 int total = 0;
404 // Handle case when there are more destination samples than source samples (upsampling).
405 immutable ResampleReal halfWidth = filterSupport*filterScale;
406 // Find the source sample(s) that contribute to each destination sample.
407 foreach (immutable i; 0..dstX) {
408 // Convert from discrete to continuous coordinates, scale, then convert back to discrete.
409 ResampleReal center = (cast(ResampleReal)i+NUDGE)/xscale;
410 center -= NUDGE;
411 center += srcOfs;
412 immutable int left = castToInt(cast(ResampleReal)floor(center-halfWidth));
413 immutable int right = castToInt(cast(ResampleReal)ceil(center+halfWidth));
414 PcontribBounds[i].center = center;
415 PcontribBounds[i].left = left;
416 PcontribBounds[i].right = right;
417 total += (right-left+1);
420 // Allocate memory for contributors.
421 if (total == 0 || ((Pcpool = cast(Contrib*)calloc(total, Contrib.sizeof)) is null)) return null;
422 //scope(failure) free(Pcpool);
424 PcpoolNext = Pcpool;
426 // Create the list of source samples which contribute to each destination sample.
427 foreach (immutable i; 0..dstX) {
428 int maxK = -1;
429 ResampleReal maxW = -1e+20f;
431 ResampleReal center = PcontribBounds[i].center;
432 immutable int left = PcontribBounds[i].left;
433 immutable int right = PcontribBounds[i].right;
435 Pcontrib[i].n = 0;
436 Pcontrib[i].p = PcpoolNext;
437 PcpoolNext += (right-left+1);
438 assert(PcpoolNext-Pcpool <= total);
440 ResampleReal totalWeight0 = 0;
441 foreach (immutable j; left..right+1) totalWeight0 += Pfilter((center-cast(ResampleReal)j)*ooFilterScale);
442 immutable ResampleReal norm = cast(ResampleReal)(1.0f/totalWeight0);
444 ResampleReal totalWeight1 = 0;
445 foreach (immutable j; left..right+1) {
446 immutable ResampleReal weight = Pfilter((center-cast(ResampleReal)j)*ooFilterScale)*norm;
447 if (weight == 0.0f) continue;
448 immutable int n = reflect(j, srcX, boundaryOp);
449 // Increment the number of source samples which contribute to the current destination sample.
450 immutable int k = Pcontrib[i].n++;
451 Pcontrib[i].p[k].pixel = cast(ushort)(n); // store src sample number
452 Pcontrib[i].p[k].weight = weight; // store src sample weight
453 totalWeight1 += weight; // total weight of all contributors
454 if (weight > maxW) {
455 maxW = weight;
456 maxK = k;
459 //assert(Pcontrib[i].n);
460 //assert(max_k != -1);
461 if (maxK == -1 || Pcontrib[i].n == 0) return null;
462 if (totalWeight1 != 1.0f) Pcontrib[i].p[maxK].weight += 1.0f-totalWeight1;
465 // don't free return value
466 PcontribRes = Pcontrib;
467 Pcontrib = null;
468 return PcontribRes;
471 static int countOps (const(ContribList)* Pclist, int k) {
472 int t = 0;
473 foreach (immutable i; 0..k) t += Pclist[i].n;
474 return t;
477 private ResampleReal mLo;
478 private ResampleReal mHi;
480 ResampleReal clampSample (ResampleReal f) const {
481 pragma(inline, true);
482 if (f < mLo) f = mLo; else if (f > mHi) f = mHi;
483 return f;
486 public:
487 // src_x/src_y - Input dimensions
488 // dst_x/dst_y - Output dimensions
489 // boundary_op - How to sample pixels near the image boundaries
490 // sample_low/sample_high - Clamp output samples to specified range, or disable clamping if sample_low >= sample_high
491 // Pclist_x/Pclist_y - Optional pointers to contributor lists from another instance of a Resampler
492 // src_x_ofs/src_y_ofs - Offset input image by specified amount (fractional values okay)
493 this(
494 int srcX, int srcY,
495 int dstX, int dstY,
496 BoundaryOp boundaryOp=BoundaryClamp,
497 ResampleReal sampleLow=0.0f, ResampleReal sampleHigh=0.0f,
498 int PfilterIndex=-1,
499 ContribList* PclistX=null,
500 ContribList* PclistY=null,
501 ResampleReal filterXScale=1.0f,
502 ResampleReal filterYScale=1.0f,
503 ResampleReal srcXOfs=0.0f,
504 ResampleReal srcYOfs=0.0f)
506 import core.stdc.stdlib : calloc, malloc;
508 int i, j;
509 ResampleReal support;
510 FilterFunc func;
512 assert(srcX > 0);
513 assert(srcY > 0);
514 assert(dstX > 0);
515 assert(dstY > 0);
517 mLo = sampleLow;
518 mHi = sampleHigh;
520 mDelayXResample = false;
521 mIntermediateX = 0;
522 mPdstBuf = null;
523 mPtmpBuf = null;
524 mClistXForced = false;
525 mPclistX = null;
526 mClistYForced = false;
527 mPclistY = null;
528 mPsrcYCount = null;
529 mPsrcYFlag = null;
530 mPscanBuf = null;
531 mStatus = StatusOkay;
533 mResampleSrcX = srcX;
534 mResampleSrcY = srcY;
535 mResampleDstX = dstX;
536 mResampleDstY = dstY;
538 mBoundaryOp = boundaryOp;
540 if ((mPdstBuf = cast(Sample*)malloc(mResampleDstX*Sample.sizeof)) is null) {
541 mStatus = StatusOutOfMemory;
542 return;
545 if (PfilterIndex < 0 || PfilterIndex >= NumFilters) {
546 PfilterIndex = resamplerFindFilterInternal(ResamplerDefaultFilter);
547 if (PfilterIndex < 0 || PfilterIndex >= NumFilters) {
548 mStatus = StatusBadFilterName;
549 return;
553 func = gFilters[PfilterIndex].func;
554 support = gFilters[PfilterIndex].support;
556 // Create contributor lists, unless the user supplied custom lists.
557 if (PclistX is null) {
558 mPclistX = makeClist(mResampleSrcX, mResampleDstX, mBoundaryOp, func, support, filterXScale, srcXOfs);
559 if (mPclistX is null) {
560 mStatus = StatusOutOfMemory;
561 return;
563 } else {
564 mPclistX = PclistX;
565 mClistXForced = true;
568 if (PclistY is null) {
569 mPclistY = makeClist(mResampleSrcY, mResampleDstY, mBoundaryOp, func, support, filterYScale, srcYOfs);
570 if (mPclistY is null) {
571 mStatus = StatusOutOfMemory;
572 return;
574 } else {
575 mPclistY = PclistY;
576 mClistYForced = true;
579 if ((mPsrcYCount = cast(int*)calloc(mResampleSrcY, int.sizeof)) is null) {
580 mStatus = StatusOutOfMemory;
581 return;
584 if ((mPsrcYFlag = cast(ubyte*)calloc(mResampleSrcY, ubyte.sizeof)) is null) {
585 mStatus = StatusOutOfMemory;
586 return;
589 // Count how many times each source line contributes to a destination line.
590 for (i = 0; i < mResampleDstY; ++i) {
591 for (j = 0; j < mPclistY[i].n; ++j) {
592 ++mPsrcYCount[resamplerRangeCheck(mPclistY[i].p[j].pixel, mResampleSrcY)];
596 if ((mPscanBuf = cast(ScanBuf*)malloc(ScanBuf.sizeof)) is null) {
597 mStatus = StatusOutOfMemory;
598 return;
601 for (i = 0; i < MaxScanBufSize; ++i) {
602 mPscanBuf.scanBufY.ptr[i] = -1;
603 mPscanBuf.scanBufL.ptr[i] = null;
606 mCurSrcY = mCurDstY = 0;
608 // Determine which axis to resample first by comparing the number of multiplies required
609 // for each possibility.
610 int xOps = countOps(mPclistX, mResampleDstX);
611 int yOps = countOps(mPclistY, mResampleDstY);
613 // Hack 10/2000: Weight Y axis ops a little more than X axis ops.
614 // (Y axis ops use more cache resources.)
615 int xyOps = xOps*mResampleSrcY+(4*yOps*mResampleDstX)/3;
616 int yxOps = (4*yOps*mResampleSrcX)/3+xOps*mResampleDstY;
618 // Now check which resample order is better. In case of a tie, choose the order
619 // which buffers the least amount of data.
620 if (xyOps > yxOps || (xyOps == yxOps && mResampleSrcX < mResampleDstX)) {
621 mDelayXResample = true;
622 mIntermediateX = mResampleSrcX;
623 } else {
624 mDelayXResample = false;
625 mIntermediateX = mResampleDstX;
629 if (mDelayXResample) {
630 if ((mPtmpBuf = cast(Sample*)malloc(mIntermediateX*Sample.sizeof)) is null) {
631 mStatus = StatusOutOfMemory;
632 return;
637 ~this () {
638 import core.stdc.stdlib : free;
640 if (mPdstBuf !is null) {
641 free(mPdstBuf);
642 mPdstBuf = null;
645 if (mPtmpBuf !is null) {
646 free(mPtmpBuf);
647 mPtmpBuf = null;
650 // Don't deallocate a contibutor list if the user passed us one of their own.
651 if (mPclistX !is null && !mClistXForced) {
652 free(mPclistX.p);
653 free(mPclistX);
654 mPclistX = null;
656 if (mPclistY !is null && !mClistYForced) {
657 free(mPclistY.p);
658 free(mPclistY);
659 mPclistY = null;
662 if (mPsrcYCount !is null) {
663 free(mPsrcYCount);
664 mPsrcYCount = null;
667 if (mPsrcYFlag !is null) {
668 free(mPsrcYFlag);
669 mPsrcYFlag = null;
672 if (mPscanBuf !is null) {
673 foreach (immutable i; 0..MaxScanBufSize) if (mPscanBuf.scanBufL.ptr[i] !is null) free(mPscanBuf.scanBufL.ptr[i]);
674 free(mPscanBuf);
675 mPscanBuf = null;
679 // Reinits resampler so it can handle another frame.
680 void restart () {
681 import core.stdc.stdlib : free;
682 if (StatusOkay != mStatus) return;
683 mCurSrcY = mCurDstY = 0;
684 foreach (immutable i; 0..mResampleSrcY) {
685 mPsrcYCount[i] = 0;
686 mPsrcYFlag[i] = false;
688 foreach (immutable i; 0..mResampleDstY) {
689 foreach (immutable j; 0..mPclistY[i].n) {
690 ++mPsrcYCount[resamplerRangeCheck(mPclistY[i].p[j].pixel, mResampleSrcY)];
693 foreach (immutable i; 0..MaxScanBufSize) {
694 mPscanBuf.scanBufY.ptr[i] = -1;
695 free(mPscanBuf.scanBufL.ptr[i]);
696 mPscanBuf.scanBufL.ptr[i] = null;
700 // false on out of memory.
701 bool putLine (const(Sample)* Psrc) {
702 int i;
704 if (mCurSrcY >= mResampleSrcY) return false;
706 // Does this source line contribute to any destination line? if not, exit now.
707 if (!mPsrcYCount[resamplerRangeCheck(mCurSrcY, mResampleSrcY)]) {
708 ++mCurSrcY;
709 return true;
712 // Find an empty slot in the scanline buffer. (FIXME: Perf. is terrible here with extreme scaling ratios.)
713 for (i = 0; i < MaxScanBufSize; ++i) if (mPscanBuf.scanBufY.ptr[i] == -1) break;
715 // If the buffer is full, exit with an error.
716 if (i == MaxScanBufSize) {
717 mStatus = StatusScanBufferFull;
718 return false;
721 mPsrcYFlag[resamplerRangeCheck(mCurSrcY, mResampleSrcY)] = true;
722 mPscanBuf.scanBufY.ptr[i] = mCurSrcY;
724 // Does this slot have any memory allocated to it?
725 if (!mPscanBuf.scanBufL.ptr[i]) {
726 import core.stdc.stdlib : malloc;
727 if ((mPscanBuf.scanBufL.ptr[i] = cast(Sample*)malloc(mIntermediateX*Sample.sizeof)) is null) {
728 mStatus = StatusOutOfMemory;
729 return false;
733 // Resampling on the X axis first?
734 if (mDelayXResample) {
735 import core.stdc.string : memcpy;
736 assert(mIntermediateX == mResampleSrcX);
737 // Y-X resampling order
738 memcpy(mPscanBuf.scanBufL.ptr[i], Psrc, mIntermediateX*Sample.sizeof);
739 } else {
740 assert(mIntermediateX == mResampleDstX);
741 // X-Y resampling order
742 resampleX(mPscanBuf.scanBufL.ptr[i], Psrc);
745 ++mCurSrcY;
747 return true;
750 // null if no scanlines are currently available (give the resampler more scanlines!)
751 const(Sample)* getLine () {
752 // if all the destination lines have been generated, then always return null
753 if (mCurDstY == mResampleDstY) return null;
754 // check to see if all the required contributors are present, if not, return null
755 foreach (immutable i; 0..mPclistY[mCurDstY].n) {
756 if (!mPsrcYFlag[resamplerRangeCheck(mPclistY[mCurDstY].p[i].pixel, mResampleSrcY)]) return null;
758 resampleY(mPdstBuf);
759 ++mCurDstY;
760 return mPdstBuf;
763 @property Status status () const { pragma(inline, true); return mStatus; }
765 // returned contributor lists can be shared with another Resampler
766 void getClists (ContribList** ptrClistX, ContribList** ptrClistY) {
767 if (ptrClistX !is null) *ptrClistX = mPclistX;
768 if (ptrClistY !is null) *ptrClistY = mPclistY;
771 @property ContribList* getClistX () { pragma(inline, true); return mPclistX; }
772 @property ContribList* getClistY () { pragma(inline, true); return mPclistY; }
774 // filter accessors
775 static @property auto filters () {
776 static struct FilterRange {
777 pure nothrow @trusted @nogc:
778 int idx;
779 @property bool empty () const { pragma(inline, true); return (idx >= NumFilters); }
780 @property string front () const { pragma(inline, true); return (idx < NumFilters ? gFilters[idx].name : null); }
781 void popFront () { if (idx < NumFilters) ++idx; }
782 int length () const { return cast(int)NumFilters; }
783 alias opDollar = length;
785 return FilterRange();
788 private:
789 /* Ensure that the contributing source sample is
790 * within bounds. If not, reflect, clamp, or wrap.
792 int reflect (in int j, in int srcX, in BoundaryOp boundaryOp) {
793 int n;
794 if (j < 0) {
795 if (boundaryOp == BoundaryReflect) {
796 n = -j;
797 if (n >= srcX) n = srcX-1;
798 } else if (boundaryOp == BoundaryWrap) {
799 n = posmod(j, srcX);
800 } else {
801 n = 0;
803 } else if (j >= srcX) {
804 if (boundaryOp == BoundaryReflect) {
805 n = (srcX-j)+(srcX-1);
806 if (n < 0) n = 0;
807 } else if (boundaryOp == BoundaryWrap) {
808 n = posmod(j, srcX);
809 } else {
810 n = srcX-1;
812 } else {
813 n = j;
815 return n;
818 void resampleX (Sample* Pdst, const(Sample)* Psrc) {
819 assert(Pdst);
820 assert(Psrc);
822 Sample total;
823 ContribList *Pclist = mPclistX;
824 Contrib *p;
826 for (int i = mResampleDstX; i > 0; --i, ++Pclist) {
827 int j = void;
828 for (j = Pclist.n, p = Pclist.p, total = 0; j > 0; --j, ++p) total += Psrc[p.pixel]*p.weight;
829 *Pdst++ = total;
833 void scaleYMov (Sample* Ptmp, const(Sample)* Psrc, ResampleReal weight, int dstX) {
834 // Not += because temp buf wasn't cleared.
835 for (int i = dstX; i > 0; --i) *Ptmp++ = *Psrc++*weight;
838 void scaleYAdd (Sample* Ptmp, const(Sample)* Psrc, ResampleReal weight, int dstX) {
839 for (int i = dstX; i > 0; --i) (*Ptmp++) += *Psrc++*weight;
842 void clamp (Sample* Pdst, int n) {
843 while (n > 0) {
844 *Pdst = clampSample(*Pdst);
845 ++Pdst;
846 --n;
850 void resampleY (Sample* Pdst) {
851 Sample* Psrc;
852 ContribList* Pclist = &mPclistY[mCurDstY];
854 Sample* Ptmp = mDelayXResample ? mPtmpBuf : Pdst;
855 assert(Ptmp);
857 // process each contributor
858 foreach (immutable i; 0..Pclist.n) {
859 // locate the contributor's location in the scan buffer -- the contributor must always be found!
860 int j = void;
861 for (j = 0; j < MaxScanBufSize; ++j) if (mPscanBuf.scanBufY.ptr[j] == Pclist.p[i].pixel) break;
862 assert(j < MaxScanBufSize);
863 Psrc = mPscanBuf.scanBufL.ptr[j];
864 if (!i) {
865 scaleYMov(Ptmp, Psrc, Pclist.p[i].weight, mIntermediateX);
866 } else {
867 scaleYAdd(Ptmp, Psrc, Pclist.p[i].weight, mIntermediateX);
870 /* If this source line doesn't contribute to any
871 * more destination lines then mark the scanline buffer slot
872 * which holds this source line as free.
873 * (The max. number of slots used depends on the Y
874 * axis sampling factor and the scaled filter width.)
877 if (--mPsrcYCount[resamplerRangeCheck(Pclist.p[i].pixel, mResampleSrcY)] == 0) {
878 mPsrcYFlag[resamplerRangeCheck(Pclist.p[i].pixel, mResampleSrcY)] = false;
879 mPscanBuf.scanBufY.ptr[j] = -1;
883 // now generate the destination line
884 if (mDelayXResample) {
885 // X was resampling delayed until after Y resampling
886 assert(Pdst != Ptmp);
887 resampleX(Pdst, Ptmp);
888 } else {
889 assert(Pdst == Ptmp);
892 if (mLo < mHi) clamp(Pdst, mResampleDstX);
897 // ////////////////////////////////////////////////////////////////////////// //
898 private nothrow @trusted @nogc:
899 int resamplerRangeCheck (int v, int h) {
900 version(assert) {
901 //import std.conv : to;
902 //assert(v >= 0 && v < h, "invalid v ("~to!string(v)~"), should be in [0.."~to!string(h)~")");
903 assert(v >= 0 && v < h); // alas, @nogc
904 return v;
905 } else {
906 pragma(inline, true);
907 return v;
911 enum M_PI = 3.14159265358979323846;
913 // Float to int cast with truncation.
914 int castToInt (Resampler.ResampleReal i) { pragma(inline, true); return cast(int)i; }
916 // (x mod y) with special handling for negative x values.
917 int posmod (int x, int y) {
918 pragma(inline, true);
919 if (x >= 0) {
920 return (x%y);
921 } else {
922 int m = (-x)%y;
923 if (m != 0) m = y-m;
924 return m;
928 // To add your own filter, insert the new function below and update the filter table.
929 // There is no need to make the filter function particularly fast, because it's
930 // only called during initializing to create the X and Y axis contributor tables.
932 /* pulse/Fourier window */
933 enum BoxFilterSupport = 0.5f;
934 Resampler.ResampleReal boxFilter (Resampler.ResampleReal t) {
935 // make_clist() calls the filter function with t inverted (pos = left, neg = right)
936 if (t >= -0.5f && t < 0.5f) return 1.0f; else return 0.0f;
939 /* box (*) box, bilinear/triangle */
940 enum TentFilterSupport = 1.0f;
941 Resampler.ResampleReal tentFilter (Resampler.ResampleReal t) {
942 if (t < 0.0f) t = -t;
943 if (t < 1.0f) return 1.0f-t; else return 0.0f;
946 /* box (*) box (*) box */
947 enum BellSupport = 1.5f;
948 Resampler.ResampleReal bellFilter (Resampler.ResampleReal t) {
949 if (t < 0.0f) t = -t;
950 if (t < 0.5f) return (0.75f-(t*t));
951 if (t < 1.5f) { t = (t-1.5f); return (0.5f*(t*t)); }
952 return (0.0f);
955 /* box (*) box (*) box (*) box */
956 enum BSplineSupport = 2.0f;
957 Resampler.ResampleReal BSplineFilter (Resampler.ResampleReal t) {
958 if (t < 0.0f) t = -t;
959 if (t < 1.0f) { immutable Resampler.ResampleReal tt = t*t; return ((0.5f*tt*t)-tt+(2.0f/3.0f)); }
960 if (t < 2.0f) { t = 2.0f-t; return ((1.0f/6.0f)*(t*t*t)); }
961 return 0.0f;
964 // Dodgson, N., "Quadratic Interpolation for Image Resampling"
965 enum QuadraticSupport = 1.5f;
966 Resampler.ResampleReal quadratic (Resampler.ResampleReal t, in Resampler.ResampleReal R) {
967 pragma(inline, true);
968 if (t < 0.0f) t = -t;
969 if (t < QuadraticSupport) {
970 immutable Resampler.ResampleReal tt = t*t;
971 if (t <= 0.5f) return (-2.0f*R)*tt+0.5f*(R+1.0f);
972 return (R*tt)+(-2.0f*R-0.5f)*t+(3.0f/4.0f)*(R+1.0f);
974 return 0.0f;
977 Resampler.ResampleReal quadraticInterpFilter (Resampler.ResampleReal t) {
978 return quadratic(t, 1.0f);
981 Resampler.ResampleReal quadraticApproxFilter (Resampler.ResampleReal t) {
982 return quadratic(t, 0.5f);
985 Resampler.ResampleReal quadraticMixFilter (Resampler.ResampleReal t) {
986 return quadratic(t, 0.8f);
989 // Mitchell, D. and A. Netravali, "Reconstruction Filters in Computer Graphics."
990 // Computer Graphics, Vol. 22, No. 4, pp. 221-228.
991 // (B, C)
992 // (1/3, 1/3) - Defaults recommended by Mitchell and Netravali
993 // (1, 0) - Equivalent to the Cubic B-Spline
994 // (0, 0.5) - Equivalent to the Catmull-Rom Spline
995 // (0, C) - The family of Cardinal Cubic Splines
996 // (B, 0) - Duff's tensioned B-Splines.
997 Resampler.ResampleReal mitchell (Resampler.ResampleReal t, in Resampler.ResampleReal B, in Resampler.ResampleReal C) {
998 Resampler.ResampleReal tt = t*t;
999 if (t < 0.0f) t = -t;
1000 if (t < 1.0f) {
1001 t = (((12.0f-9.0f*B-6.0f*C)*(t*tt))+
1002 ((-18.0f+12.0f*B+6.0f*C)*tt)+
1003 (6.0f-2.0f*B));
1004 return (t/6.0f);
1006 if (t < 2.0f) {
1007 t = (((-1.0f*B-6.0f*C)*(t*tt))+
1008 ((6.0f*B+30.0f*C)*tt)+
1009 ((-12.0f*B-48.0f*C)*t)+
1010 (8.0f*B+24.0f*C));
1011 return (t/6.0f);
1013 return 0.0f;
1016 enum MitchellSupport = 2.0f;
1017 Resampler.ResampleReal mitchellFilter (Resampler.ResampleReal t) {
1018 return mitchell(t, 1.0f/3.0f, 1.0f/3.0f);
1021 enum CatmullRomSupport = 2.0f;
1022 Resampler.ResampleReal catmullRomFilter (Resampler.ResampleReal t) {
1023 return mitchell(t, 0.0f, 0.5f);
1026 double sinc (double x) {
1027 pragma(inline, true);
1028 import std.math : sin;
1029 x *= M_PI;
1030 if (x < 0.01f && x > -0.01f) return 1.0f+x*x*(-1.0f/6.0f+x*x*1.0f/120.0f);
1031 return sin(x)/x;
1034 Resampler.ResampleReal clean (double t) {
1035 pragma(inline, true);
1036 import std.math : abs;
1037 enum EPSILON = cast(Resampler.ResampleReal)0.0000125f;
1038 if (abs(t) < EPSILON) return 0.0f;
1039 return cast(Resampler.ResampleReal)t;
1042 //static double blackman_window(double x)
1044 // return 0.42f+0.50f*cos(M_PI*x)+0.08f*cos(2.0f*M_PI*x);
1047 double blackmanExactWindow (double x) {
1048 pragma(inline, true);
1049 import std.math : cos;
1050 return 0.42659071f+0.49656062f*cos(M_PI*x)+0.07684867f*cos(2.0f*M_PI*x);
1053 enum BlackmanSupport = 3.0f;
1054 Resampler.ResampleReal blackmanFilter (Resampler.ResampleReal t) {
1055 if (t < 0.0f) t = -t;
1056 if (t < 3.0f) {
1057 //return clean(sinc(t)*blackman_window(t/3.0f));
1058 return clean(sinc(t)*blackmanExactWindow(t/3.0f));
1060 return (0.0f);
1063 // with blackman window
1064 enum GaussianSupport = 1.25f;
1065 Resampler.ResampleReal gaussianFilter (Resampler.ResampleReal t) {
1066 import std.math : exp, sqrt;
1067 if (t < 0) t = -t;
1068 if (t < GaussianSupport) return clean(exp(-2.0f*t*t)*sqrt(2.0f/M_PI)*blackmanExactWindow(t/GaussianSupport));
1069 return 0.0f;
1072 // Windowed sinc -- see "Jimm Blinn's Corner: Dirty Pixels" pg. 26.
1073 enum Lanczos3Support = 3.0f;
1074 Resampler.ResampleReal lanczos3Filter (Resampler.ResampleReal t) {
1075 if (t < 0.0f) t = -t;
1076 if (t < 3.0f) return clean(sinc(t)*sinc(t/3.0f));
1077 return (0.0f);
1080 enum Lanczos4Support = 4.0f;
1081 Resampler.ResampleReal lanczos4Filter (Resampler.ResampleReal t) {
1082 if (t < 0.0f) t = -t;
1083 if (t < 4.0f) return clean(sinc(t)*sinc(t/4.0f));
1084 return (0.0f);
1087 enum Lanczos6Support = 6.0f;
1088 Resampler.ResampleReal lanczos6Filter (Resampler.ResampleReal t) {
1089 if (t < 0.0f) t = -t;
1090 if (t < 6.0f) return clean(sinc(t)*sinc(t/6.0f));
1091 return (0.0f);
1094 enum Lanczos12Support = 12.0f;
1095 Resampler.ResampleReal lanczos12Filter (Resampler.ResampleReal t) {
1096 if (t < 0.0f) t = -t;
1097 if (t < 12.0f) return clean(sinc(t)*sinc(t/12.0f));
1098 return (0.0f);
1101 double bessel0 (double x) {
1102 enum EpsilonRatio = cast(double)1E-16;
1103 double xh = 0.5*x;
1104 double sum = 1.0;
1105 double pow = 1.0;
1106 int k = 0;
1107 double ds = 1.0;
1108 // FIXME: Shouldn't this stop after X iterations for max. safety?
1109 while (ds > sum*EpsilonRatio) {
1110 ++k;
1111 pow = pow*(xh/k);
1112 ds = pow*pow;
1113 sum = sum+ds;
1115 return sum;
1118 enum KaiserAlpha = cast(Resampler.ResampleReal)4.0;
1119 double kaiser (double alpha, double halfWidth, double x) {
1120 pragma(inline, true);
1121 import std.math : sqrt;
1122 immutable double ratio = (x/halfWidth);
1123 return bessel0(alpha*sqrt(1-ratio*ratio))/bessel0(alpha);
1126 enum KaiserSupport = 3;
1127 static Resampler.ResampleReal kaiserFilter (Resampler.ResampleReal t) {
1128 if (t < 0.0f) t = -t;
1129 if (t < KaiserSupport) {
1130 import std.math : exp, log;
1131 // db atten
1132 immutable Resampler.ResampleReal att = 40.0f;
1133 immutable Resampler.ResampleReal alpha = cast(Resampler.ResampleReal)(exp(log(cast(double)0.58417*(att-20.96))*0.4)+0.07886*(att-20.96));
1134 //const Resampler.Resample_Real alpha = KAISER_ALPHA;
1135 return cast(Resampler.ResampleReal)clean(sinc(t)*kaiser(alpha, KaiserSupport, t));
1137 return 0.0f;
1140 // filters[] is a list of all the available filter functions.
1141 struct FilterInfo {
1142 string name;
1143 Resampler.FilterFunc func;
1144 Resampler.ResampleReal support;
1147 static immutable FilterInfo[16] gFilters = [
1148 FilterInfo("box", &boxFilter, BoxFilterSupport),
1149 FilterInfo("tent", &tentFilter, TentFilterSupport),
1150 FilterInfo("bell", &bellFilter, BellSupport),
1151 FilterInfo("bspline", &BSplineFilter, BSplineSupport),
1152 FilterInfo("mitchell", &mitchellFilter, MitchellSupport),
1153 FilterInfo("lanczos3", &lanczos3Filter, Lanczos3Support),
1154 FilterInfo("blackman", &blackmanFilter, BlackmanSupport),
1155 FilterInfo("lanczos4", &lanczos4Filter, Lanczos4Support),
1156 FilterInfo("lanczos6", &lanczos6Filter, Lanczos6Support),
1157 FilterInfo("lanczos12", &lanczos12Filter, Lanczos12Support),
1158 FilterInfo("kaiser", &kaiserFilter, KaiserSupport),
1159 FilterInfo("gaussian", &gaussianFilter, GaussianSupport),
1160 FilterInfo("catmullrom", &catmullRomFilter, CatmullRomSupport),
1161 FilterInfo("quadratic_interp", &quadraticInterpFilter, QuadraticSupport),
1162 FilterInfo("quadratic_approx", &quadraticApproxFilter, QuadraticSupport),
1163 FilterInfo("quadratic_mix", &quadraticMixFilter, QuadraticSupport),
1166 enum NumFilters = cast(int)gFilters.length;
1169 bool rsmStringEqu (const(char)[] s0, const(char)[] s1) {
1170 for (;;) {
1171 if (s0.length && (s0.ptr[0] <= ' ' || s0.ptr[0] == '_')) { s0 = s0[1..$]; continue; }
1172 if (s1.length && (s1.ptr[0] <= ' ' || s1.ptr[0] == '_')) { s1 = s1[1..$]; continue; }
1173 if (s0.length == 0) {
1174 while (s1.length && (s1.ptr[0] <= ' ' || s1.ptr[0] == '_')) s1 = s1[1..$];
1175 return (s1.length == 0);
1177 if (s1.length == 0) {
1178 while (s0.length && (s0.ptr[0] <= ' ' || s0.ptr[0] == '_')) s0 = s0[1..$];
1179 return (s0.length == 0);
1181 assert(s0.length && s1.length);
1182 char c0 = s0.ptr[0];
1183 char c1 = s1.ptr[0];
1184 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower
1185 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower
1186 if (c0 != c1) return false;
1187 s0 = s0[1..$];
1188 s1 = s1[1..$];
1193 int resamplerFindFilterInternal (const(char)[] name) {
1194 if (name.length) {
1195 foreach (immutable idx, const ref fi; gFilters[]) if (rsmStringEqu(name, fi.name)) return cast(int)idx;
1197 return -1;