1 // Separable filtering image rescaler v2.21, Rich Geldreich - richgel99@gmail.com
3 // This is free and unencumbered software released into the public domain.
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
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.
37 module iv
.iresample
/*is aliced*/;
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");
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
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
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
);
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;
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
;
152 foreach (ref rsm; resamplers
[]) delete rsm;
153 foreach (ref smr
; samples
[]) delete smr
;
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
;
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
) {
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");
189 for (; compIdx
< Components
; ++compIdx
) {
190 const(float)* outsmp
= resamplers
.ptr
[compIdx
].getLine();
191 if (outsmp
is null) break;
192 auto dsc
= dstrow
.ptr
;
194 static if (Components
== 4) {
196 foreach (immutable x
; 0..dstwdt
) {
197 dsc
.a
= clampToByte(cast(int)(255.0f*(*outsmp
++)+0.5f));
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
];
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
;
223 //version(iresample_debug) { import core.stdc.stdio; stderr.fprintf("writing dest row %d with %u components\n", dsty, Components); }
224 dstPutRow(dsty
, dstrow
);
231 // ////////////////////////////////////////////////////////////////////////// //
232 public final class Resampler
{
233 nothrow @trusted @nogc:
235 alias ResampleReal
= float;
236 alias Sample
= ResampleReal
;
238 static struct Contrib
{
243 static struct ContribList
{
248 alias BoundaryOp
= int;
249 enum /*Boundary_Op*/ {
258 StatusOutOfMemory
= 1,
259 StatusBadFilterName
= 2,
260 StatusScanBufferFull
= 3,
264 alias FilterFunc
= ResampleReal
function (ResampleReal
) nothrow @trusted @nogc;
273 BoundaryOp mBoundaryOp
;
278 ContribList
* mPclistX
;
279 ContribList
* mPclistY
;
284 bool mDelayXResample
;
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
;
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
,
309 ResampleReal filterSupport
,
310 ResampleReal filterScale
,
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).
322 ContribList
* Pcontrib
, PcontribRes
;
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
;
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
;
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;
364 // Create the list of source samples which contribute to each destination sample.
365 foreach (immutable i
; 0..dstX
) {
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
;
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
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
;
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
;
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);
426 // Create the list of source samples which contribute to each destination sample.
427 foreach (immutable i
; 0..dstX
) {
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
;
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
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
;
471 static int countOps (const(ContribList
)* Pclist
, int k
) {
473 foreach (immutable i
; 0..k
) t
+= Pclist
[i
].n
;
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
;
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)
496 BoundaryOp boundaryOp
=BoundaryClamp
,
497 ResampleReal sampleLow
=0.0f, ResampleReal sampleHigh
=0.0f,
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
;
509 ResampleReal support
;
520 mDelayXResample
= false;
524 mClistXForced
= false;
526 mClistYForced
= false;
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
;
545 if (PfilterIndex
< 0 || PfilterIndex
>= NumFilters
) {
546 PfilterIndex
= resamplerFindFilterInternal(ResamplerDefaultFilter
);
547 if (PfilterIndex
< 0 || PfilterIndex
>= NumFilters
) {
548 mStatus
= StatusBadFilterName
;
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
;
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
;
576 mClistYForced
= true;
579 if ((mPsrcYCount
= cast(int*)calloc(mResampleSrcY
, int.sizeof
)) is null) {
580 mStatus
= StatusOutOfMemory
;
584 if ((mPsrcYFlag
= cast(ubyte*)calloc(mResampleSrcY
, ubyte.sizeof
)) is null) {
585 mStatus
= StatusOutOfMemory
;
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
;
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
;
624 mDelayXResample
= false;
625 mIntermediateX
= mResampleDstX
;
629 if (mDelayXResample
) {
630 if ((mPtmpBuf
= cast(Sample
*)malloc(mIntermediateX
*Sample
.sizeof
)) is null) {
631 mStatus
= StatusOutOfMemory
;
638 import core
.stdc
.stdlib
: free
;
640 if (mPdstBuf
!is null) {
645 if (mPtmpBuf
!is null) {
650 // Don't deallocate a contibutor list if the user passed us one of their own.
651 if (mPclistX
!is null && !mClistXForced
) {
656 if (mPclistY
!is null && !mClistYForced
) {
662 if (mPsrcYCount
!is null) {
667 if (mPsrcYFlag
!is null) {
672 if (mPscanBuf
!is null) {
673 foreach (immutable i
; 0..MaxScanBufSize
) if (mPscanBuf
.scanBufL
.ptr
[i
] !is null) free(mPscanBuf
.scanBufL
.ptr
[i
]);
679 // Reinits resampler so it can handle another frame.
681 import core
.stdc
.stdlib
: free
;
682 if (StatusOkay
!= mStatus
) return;
683 mCurSrcY
= mCurDstY
= 0;
684 foreach (immutable i
; 0..mResampleSrcY
) {
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
) {
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
)]) {
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
;
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
;
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
);
740 assert(mIntermediateX
== mResampleDstX
);
741 // X-Y resampling order
742 resampleX(mPscanBuf
.scanBufL
.ptr
[i
], Psrc
);
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;
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
; }
775 static @property auto filters () {
776 static struct FilterRange
{
777 pure nothrow @trusted @nogc:
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();
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
) {
795 if (boundaryOp
== BoundaryReflect
) {
797 if (n
>= srcX
) n
= srcX
-1;
798 } else if (boundaryOp
== BoundaryWrap
) {
803 } else if (j
>= srcX
) {
804 if (boundaryOp
== BoundaryReflect
) {
805 n
= (srcX
-j
)+(srcX
-1);
807 } else if (boundaryOp
== BoundaryWrap
) {
818 void resampleX (Sample
* Pdst
, const(Sample
)* Psrc
) {
823 ContribList
*Pclist
= mPclistX
;
826 for (int i
= mResampleDstX
; i
> 0; --i
, ++Pclist
) {
828 for (j
= Pclist
.n
, p
= Pclist
.p
, total
= 0; j
> 0; --j
, ++p
) total
+= Psrc
[p
.pixel
]*p
.weight
;
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
) {
844 *Pdst
= clampSample(*Pdst
);
850 void resampleY (Sample
* Pdst
) {
852 ContribList
* Pclist
= &mPclistY
[mCurDstY
];
854 Sample
* Ptmp
= mDelayXResample ? mPtmpBuf
: Pdst
;
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!
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
];
865 scaleYMov(Ptmp
, Psrc
, Pclist
.p
[i
].weight
, mIntermediateX
);
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
);
889 assert(Pdst
== Ptmp
);
892 if (mLo
< mHi
) clamp(Pdst
, mResampleDstX
);
897 // ////////////////////////////////////////////////////////////////////////// //
898 private nothrow @trusted @nogc:
899 int resamplerRangeCheck (int v
, int h
) {
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
906 pragma(inline
, true);
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);
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
)); }
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
)); }
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);
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.
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
;
1001 t
= (((12.0f-9.0f*B
-6.0f*C
)*(t
*tt
))+
1002 ((-18.0f+12.0f*B
+6.0f*C
)*tt
)+
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
)+
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
;
1030 if (x
< 0.01f && x
> -0.01f) return 1.0f+x
*x
*(-1.0f/6.0f+x
*x
*1.0f/120.0f);
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
;
1057 //return clean(sinc(t)*blackman_window(t/3.0f));
1058 return clean(sinc(t
)*blackmanExactWindow(t
/3.0f));
1063 // with blackman window
1064 enum GaussianSupport
= 1.25f;
1065 Resampler
.ResampleReal
gaussianFilter (Resampler
.ResampleReal t
) {
1066 import std
.math
: exp
, sqrt
;
1068 if (t
< GaussianSupport
) return clean(exp(-2.0f*t
*t
)*sqrt(2.0f/M_PI
)*blackmanExactWindow(t
/GaussianSupport
));
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));
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));
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));
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));
1101 double bessel0 (double x
) {
1102 enum EpsilonRatio
= cast(double)1E-16;
1108 // FIXME: Shouldn't this stop after X iterations for max. safety?
1109 while (ds > sum
*EpsilonRatio
) {
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
;
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
));
1140 // filters[] is a list of all the available filter functions.
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
) {
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;
1193 int resamplerFindFilterInternal (const(char)[] name
) {
1195 foreach (immutable idx
, const ref fi
; gFilters
[]) if (rsmStringEqu(name
, fi
.name
)) return cast(int)idx
;