Painter improvements: Multithreaded rendering improved, new image filter (like Lanczos 3) option, image mapping is now more precise
This commit is contained in:
mirek-fidler 2023-12-24 15:21:23 +01:00 committed by GitHub
parent eb54503041
commit 2d0f19053a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 10281 additions and 3650 deletions

34
benchmarks/CoDo/CoDo.cpp Normal file
View file

@ -0,0 +1,34 @@
#include <Core/Core.h>
using namespace Upp;
int m;
std::atomic<int> ii(0);
Function<void ()> x;
Mutex mtx;
CONSOLE_APP_MAIN
{
for(int i = 0; i < 100000; i++) {
RTIMING("CoDo");
CoDo([&] {});
}
for(int i = 0; i < 100000; i++) {
RTIMING("Function");
x = [&] {};
}
for(int i = 0; i < 1000000; i++) {
RTIMING("atomic++");
ii++;
}
for(int i = 0; i < 1000000; i++) {
RTIMING("int++");
m++;
}
for(int i = 0; i < 1000000; i++) {
RTIMING("Mutex");
Mutex::Lock __(mtx);
}
}

9
benchmarks/CoDo/CoDo.upp Normal file
View file

@ -0,0 +1,9 @@
uses
Core;
file
CoDo.cpp;
mainconfig
"" = "";

View file

@ -1,7 +1,36 @@
TIMING Blend : 435.99 ms - 435.99 us (436.00 ms / 1000 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 1000
TIMING Stroke : 236.99 ms - 236.99 us (237.00 ms / 1000 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 1000
TIMING Fill : 471.99 ms - 471.99 us (472.00 ms / 1000 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 1000
TIMING Rect : 596.99 ms - 596.99 us (597.00 ms / 1000 ), min: 0.00 ns, max: 2.00 ms, nesting: 0 - 1000
TIMING Clear 2 : 704.99 ms - 704.99 us (705.00 ms / 1000 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 1000
TIMING Clear : 604.99 ms - 604.99 us (605.00 ms / 1000 ), min: 0.00 ns, max: 2.00 ms, nesting: 0 - 1000
TIMING DO FILL ST : 9.89 ms - 749.50 ns (10.00 ms / 13199 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13199
TIMING ApproximateChar2: 8.89 ms - 677.21 ns ( 9.00 ms / 13132 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13132
TIMING PaintCharacter : 2.00 ms - 57.13 us ( 2.00 ms / 35 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 35
TIMING ApproximateChar::Fetch: 4.89 ms - 372.62 ns ( 5.00 ms / 13132 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13132
TIMING ApproximateChar: 13.89 ms - 1.06 us (14.00 ms / 13132 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13132
TIMING CharacterOp : 893.19 us - 68.02 ns ( 1.00 ms / 13132 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13132
TIMING TextOp : 999.46 us - 14.92 us ( 1.00 ms / 67 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 67
TIMING Text : 25.00 ms - 25.00 ms (25.00 ms / 1 ), min: 25.00 ms, max: 25.00 ms, nesting: 0 - 1
TIMING Clear : 3.00 ms - 3.00 ms ( 3.00 ms / 1 ), min: 3.00 ms, max: 3.00 ms, nesting: 0 - 1
TIMING DO FILL MT : 11.00 ms - 323.52 us (11.00 ms / 34 ), min: 0.00 ns, max: 2.00 ms, nesting: 0 - 34
TIMING ApproximateChar2: 18.92 ms - 1.44 us (19.00 ms / 13132 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13132
TIMING PaintCharacter : 3.00 ms - 85.71 us ( 3.00 ms / 35 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 35
TIMING ApproximateChar::Fetch: 9.92 ms - 755.08 ns (10.00 ms / 13132 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13132
TIMING ApproximateChar: 579.92 ms - 44.16 us (580.00 ms / 13132 ), min: 0.00 ns, max: 3.00 ms, nesting: 0 - 13132
TIMING Path : 20.00 ms - 588.23 us (20.00 ms / 34 ), min: 0.00 ns, max: 3.00 ms, nesting: 0 - 34
TIMING CharacterOp : 0.00 ns - 0.00 ns ( 0.00 ns / 13132 ), min: 0.00 ns, max: 0.00 ns, nesting: 0 - 13132
TIMING TextOp : 999.57 us - 14.92 us ( 1.00 ms / 67 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 67
TIMING Text : 55.00 ms - 55.00 ms (55.00 ms / 1 ), min: 55.00 ms, max: 55.00 ms, nesting: 0 - 1
TIMING Clear : 999.99 us - 999.99 us ( 1.00 ms / 1 ), min: 1.00 ms, max: 1.00 ms, nesting: 0 - 1
TIMING DO FILL MT : 6.00 ms - 176.46 us ( 6.00 ms / 34 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 34
TIMING ApproximateChar2: 24.89 ms - 1.90 us (25.00 ms / 13132 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13132
TIMING PaintCharacter : 205.00 ms - 2.36 ms (205.00 ms / 87 ), min: 0.00 ns, max: 8.00 ms, nesting: 0 - 87
TIMING Key : 2.89 ms - 220.45 ns ( 3.00 ms / 13132 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13132
TIMING ApproximateChar: 725.89 ms - 55.28 us (726.00 ms / 13132 ), min: 0.00 ns, max: 8.00 ms, nesting: 0 - 13132
TIMING Path : 24.00 ms - 705.87 us (24.00 ms / 34 ), min: 0.00 ns, max: 8.00 ms, nesting: 0 - 34
TIMING CharacterOp : 0.00 ns - 0.00 ns ( 0.00 ns / 13132 ), min: 0.00 ns, max: 0.00 ns, nesting: 0 - 13132
TIMING TextOp : 3.00 ms - 44.77 us ( 3.00 ms / 67 ), min: 0.00 ns, max: 3.00 ms, nesting: 0 - 67
TIMING Text : 63.00 ms - 63.00 ms (63.00 ms / 1 ), min: 63.00 ms, max: 63.00 ms, nesting: 0 - 1
TIMING Clear : 2.00 ms - 2.00 ms ( 2.00 ms / 1 ), min: 2.00 ms, max: 2.00 ms, nesting: 0 - 1
TIMING Reset : 4.89 ms - 349.45 ns ( 5.00 ms / 13988 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 13988
TIMING Alloc : 31.99 ms - 40.55 us (32.00 ms / 789 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 789
TIMING Free : 6.99 ms - 4.43 us ( 7.00 ms / 1578 ), min: 0.00 ns, max: 1.00 ms, nesting: 0 - 1578
TIMING GetGlyphInfoSys DO: 11.00 ms - 3.67 ms (11.00 ms / 3 ), min: 3.00 ms, max: 5.00 ms, nesting: 0 - 3
TIMING GetGlyphEntry : 164.00 ms - 970.41 us (164.00 ms / 169 ), min: 0.00 ns, max: 7.00 ms, nesting: 0 - 169

View file

@ -0,0 +1,33 @@
#include "Examples.h"
void ImageFilters(Painter& sw)
{
int nx = 0;
int ny = 0;
auto Do = [&](const char *name, int filter) {
Size isz = TestImg::test().GetSize();
int x = nx * 3 * isz.cx;
int y = ny * 3 * isz.cy;
sw.DrawText(x + 10, y, name, Arial(28));
sw.Rectangle(x + 10, y + 30, 7 * isz.cx / 3, 7 * isz.cy / 3)
.ImageFilter(filter)
.Fill(TestImg::test(), x + 10, y + 30, x + 10 + 7 * isz.cx / 3, y + 30, FILL_PAD);
nx++;
if(nx > 2) {
nx = 0;
ny++;
}
};
Do("Nearest", FILTER_NEAREST);
Do("Bilinear", FILTER_BILINEAR);
Do("B-spline", FILTER_BSPLINE);
Do("Costella", FILTER_COSTELLA);
Do("Bicubic Mitchell", FILTER_BICUBIC_MITCHELL);
Do("Bicubic Catmull Rom", FILTER_BICUBIC_CATMULLROM);
Do("Lanczos3", FILTER_LANCZOS3);
}
INITBLOCK {
RegisterExample("Image filters", ImageFilters);
}

View file

@ -8,6 +8,7 @@ file
Examples.h,
main.cpp,
Image.cpp,
ImageFilter.cpp,
Lion.cpp,
Pythagoras.cpp,
Spiral.cpp,

View file

@ -13,6 +13,9 @@ void DoRect(Painter &sw, double size, bool image)
sw.Fill(TestImg::test(), 0, 0, size, 0);
else
sw.Fill(Blue());
sw.Begin();
sw.Opacity(0.9);
sw.Begin();
sw.Translate(0, size);
@ -25,6 +28,8 @@ void DoRect(Painter &sw, double size, bool image)
sw.Rotate(-M_PI/4.0);
DoRect(sw, size / M_SQRT2, image);
sw.End();
sw.End();
}
void PythagorasTree(Painter& sw)

View file

@ -100,11 +100,11 @@ void App::Paint(Draw& w)
ImageBuffer ib(sz);
{
BufferPainter sw(ib, ctrl.quality);
sw.Co(ctrl.mt);
if(ctrl.transparent)
sw.Clear(RGBAZero());
else
sw.Clear(White());
sw.Co(ctrl.mt);
sw.PreClip(ctrl.preclip);
DoPaint(sw);
}
@ -207,7 +207,7 @@ App::App() {
ctrl.print <<= THISBACK(Print);
Reset();
LoadFromFile(*this);
Title("Painter");
Title("Painter 2");
}
App::~App()

View file

@ -292,7 +292,7 @@ void CoWork::Cancel()
void CoWork::Finish() {
Pool& p = GetPool();
p.lock.Enter();
while(!jobs.IsEmpty(1)) {
while(todo && !jobs.IsEmpty(1)) {
LLOG("Finish: todo: " << todo << " (CoWork " << FormatIntHex(this) << ")");
p.DoJob(*jobs.GetNext(1));
}

View file

@ -34,3 +34,78 @@ void ValueCacheAdjustSize(P getsize)
Mutex::Lock __(ValueCacheMutex);
TheValueCache().AdjustSize(getsize);
}
template <class M>
Value MakeValue_(const String& key, const M& m, int& sz)
{
struct Maker : ValueMaker {
const String& key;
const M& m;
String Key() const override {
return key;
}
int Make(Value& object) const override {
return m(object);
}
Maker(const String& key, const M& m) : key(key), m(m) {}
};
Maker maker(key, m);
return MakeValueSz(maker, sz);
}
template <class K, class M>
String MakeKey_(const K& k, const M& m)
{
StringBuffer key;
RawCat(key, StaticTypeNo<K>());
RawCat(key, StaticTypeNo<M>());
key.Cat(k());
return key;
}
template <class K, class M>
Value MakeValue(const K& k, const M& m)
{
int sz;
return MakeValue_(MakeKey_(k, m), m, sz);
}
template <class K, class M>
Value MakeValueTL(const K& k, const M& m)
{
String key = MakeKey_(k, m);
struct Maker : ValueMaker {
const String& key;
const M& m;
String Key() const override {
return key;
}
int Make(Value& object) const override {
int sz;
object = MakeValue_(key, m, sz);
return sz;
}
Maker(const String& key, const M& m) : key(key), m(m) {}
};
Maker maker(key, m);
if(IsMainThread()) {
static LRUCache<Value> cache; // this is basically to avoid problem with leaks detection
Value v = cache.Get(maker);
cache.Shrink(128 * 1024, 1000);
return v;
}
else {
thread_local LRUCache<Value> cache;
Value v = cache.Get(maker);
cache.Shrink(128 * 1024, 1000);
return v;
}
}

View file

@ -146,10 +146,9 @@ Finish should be called separately before destructor.&]
[s4; &]
[s5;:Upp`:`:CoWork`:`:GetWorkerIndex`(`): [@(0.0.255) static] [@(0.0.255) int]_[* GetWorker
Index]()&]
[s2;%% Returns the index of worker `- index is >`= 0 and < GetPoolSize().
This is useful if there is a need for per`-thread resources.
`-1 means that thread is now worker (this can happen when Finish
is using calling thread to perform jobs).&]
[s2;%% Returns the index of current worker thread `- index is >`=
0 and < GetPoolSize(). This is useful if there is a need for
per`-thread resources. `-1 means that thread is not worker.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:CoWork`:`:GetPoolSize`(`): [@(0.0.255) static] [@(0.0.255) int]_[* GetPoolSize](

View file

@ -195,7 +195,8 @@ enum {
FILTER_NEAREST = 0,
FILTER_BILINEAR = 1,
FILTER_BSPLINE = 2,
FILTER_COSTELLO = 3,
FILTER_COSTELLO = 3, // (name misspelled)
FILTER_COSTELLA = 3,
FILTER_BICUBIC_MITCHELL = 4,
FILTER_BICUBIC_CATMULLROM = 5,
FILTER_LANCZOS2 = 6,
@ -204,6 +205,26 @@ enum {
FILTER_LANCZOS5 = 9,
};
Tuple2<double (*)(double), int> GetImageFilterFunction(int filter);
struct ImageFilterKernel {
int a;
int n;
int shift;
int ashift;
int kernel_size;
const int *kernel;
double mul;
int Get(int x, int dx) const { return kernel[clamp(((x << shift) - dx) * a / n + ashift, 0, kernel_size)]; }
void Init(double (*kfn)(double x), int a, int src_sz, int tgt_sz);
void Init(int filter, int src_sz, int tgt_sz);
ImageFilterKernel() {}
ImageFilterKernel(double (*kfn)(double x), int a, int src_sz, int tgt_sz);
};
Image RescaleFilter(const Image& img, Size sz, const Rect& sr, int filter, Gate<int, int> progress = Null, bool co = false);
Image RescaleFilter(const Image& img, Size sz, int filter, Gate<int, int> progress = Null);
Image RescaleFilter(const Image& img, int cx, int cy, int filter, Gate<int, int> progress = Null);

View file

@ -4,170 +4,6 @@ namespace Upp {
#define LDUMP(x) // DUMP(x)
static const int *sGetKernel(double (*kfn)(double x), int a, int shift)
{
INTERLOCKED {
static VectorMap<Tuple3<uintptr_t, int, int>, Buffer<int> > kache;
Tuple3<uintptr_t, int, int> key = MakeTuple((uintptr_t)kfn, a, shift);
Buffer<int> *k = kache.FindPtr(key);
if(k)
return *k;
Buffer<int>& ktab = kache.GetAdd(key);
ktab.Alloc(((2 * a) << shift) + 1);
for(int i = 0; i < ((2 * a) << shift) + 1; i++)
ktab[i] = int((1 << shift) * (*kfn)((double)i / (1 << shift) - a));
return ktab;
}
return NULL;
}
force_inline
int sGetk(const int *kernel, int x, int a, int shift)
{
x += a << shift;
ASSERT(x >= 0 && x < ((2 * a) << shift) + 1);
x = minmax(x, 0, (2 * a) << shift);
return kernel[x];
}
static int sGeta(int a, int src, int tgt, int& shift)
{
int n = min(max(src / tgt, 1) * a, 31);
shift = 8 - n / 8;
return n;
}
Image RescaleFilter(const Image& img, Size sz, const Rect& sr,
double (*kfn)(double x), int a,
Gate<int, int> progress, bool co)
{
ASSERT(Rect(img.GetSize()).Contains(sr));
Size isz = sr.GetSize();
if(isz.cx <= 0 || isz.cy <= 0 || sz.cx <= 0 || sz.cy <= 0)
return Image();
int shiftx, shifty;
int ax = sGeta(a, isz.cx, sz.cx, shiftx);
int ay = sGeta(a, isz.cy, sz.cy, shifty);
int shift = min(shiftx, shifty);
const int *kernel = sGetKernel(kfn, a, shift);
Buffer<int> px(sz.cx * 2 * ax * 2 * ay * 2);
int *xd = px;
Size cr = (Size(1 << shift, 1 << shift) - (isz << shift) / sz) >> 1;
for(int x = 0; x < sz.cx; x++) {
int dx = ((x * isz.cx) << shift) / sz.cx - cr.cx;
int sx = dx >> shift;
dx -= sx << shift;
if(dx < 0)
dx = 0;
for(int yy = -ay + 1; yy <= ay; yy++)
for(int xx = -ax + 1; xx <= ax; xx++) {
*xd++ = clamp(sx + xx, 0, isz.cx - 1) + sr.left;
*xd++ = sGetk(kernel, ((xx << shift) - dx) * a / ax, a, shift);
}
}
ImageBuffer ib(sz);
std::atomic<int> yy(0);
CoDo(co, [&] {
Buffer<int> py(2 * ay * 2);
for(int y = yy++; y < sz.cy; y = yy++) {
if(progress(y, sz.cy))
break;
int dy = ((y * isz.cy) << shift) / sz.cy - cr.cy;
int sy = dy >> shift;
dy -= sy << shift;
if(dy < 0)
dy = 0;
int *xd = px;
int *yd = py;
for(int yy = -ay + 1; yy <= ay; yy++) {
*yd++ = sGetk(kernel, ((yy << shift) - dy) * a / ay, a, shift);
*yd++ = clamp(sy + yy, 0, isz.cy - 1) + sr.top;
}
RGBA *t = ib[y];
#ifdef CPU_SIMD
for(int x = 0; x < sz.cx; x++) {
f32x4 rgbaf = 0;
f32x4 w = 0;
yd = py;
for(int yy = 2 * ay; yy-- > 0;) {
int ky = *yd++;
const RGBA *l = img[*yd++];
for(int xx = 2 * ax; xx-- > 0;) {
f32x4 s = LoadRGBAF(&l[*xd++]);
f32x4 weight = f32all(float(ky * *xd++));
rgbaf += weight * s;
w += weight;
}
}
StoreRGBAF(t++, ClampRGBAF(rgbaf / w));
}
#else
for(int x = 0; x < sz.cx; x++) {
int red = 0;
int green = 0;
int blue = 0;
int alpha = 0;
int w = 0;
yd = py;
int hasalpha = 0;
for(int yy = 2 * ay; yy-- > 0;) {
int ky = *yd++;
const RGBA *l = img[*yd++];
for(int xx = 2 * ax; xx-- > 0;) {
const RGBA& s = l[*xd++];
int weight = ky * *xd++;
red += weight * s.r;
green += weight * s.g;
blue += weight * s.b;
alpha += weight * s.a;
hasalpha |= s.a - 255;
w += weight;
}
}
if(w)
if(hasalpha) {
t->a = alpha = Saturate255(alpha / w);
t->r = clamp(red / w, 0, alpha);
t->g = clamp(green / w, 0, alpha);
t->b = clamp(blue / w, 0, alpha);
}
else {
t->a = 255;
t->r = Saturate255(red / w);
t->g = Saturate255(green / w);
t->b = Saturate255(blue / w);
}
else
t->a = t->r = t->g = t->b = 0;
t++;
}
#endif
}
});
ib.SetResolution(img.GetResolution());
return ib;
}
Image RescaleFilter(const Image& img, Size sz,
double (*kfn)(double x), int a,
Gate<int, int> progress, bool co)
{
return RescaleFilter(img, sz, img.GetSize(), kfn, a, progress, co);
}
Image RescaleFilter(const Image& img, int cx, int cy,
double (*kfn)(double x), int a,
Gate<int, int> progress, bool co)
{
return RescaleFilter(img, Size(cx, cy), img.GetSize(), kfn, a, progress, co);
}
static double sNearest(double x)
{
return (double)(x >= -0.5 && x <= 0.5);
@ -238,7 +74,7 @@ static double sLanczos5(double x)
return sLanczos(x, 5);
}
static double sCostello(double x)
static double sCostella(double x)
{
x = fabs(x);
return x < 0.5 ? 0.75 - x * x :
@ -246,15 +82,13 @@ static double sCostello(double x)
0;
}
Image RescaleFilter(const Image& img, Size sz, const Rect& sr, int filter, Gate<int, int> progress, bool co)
Tuple2<double (*)(double), int> GetImageFilterFunction(int filter)
{
if(IsNull(filter))
return Rescale(img, sz, sr);
static Tuple2<double (*)(double), int> tab[] = {
{ sNearest, 1 },
{ sLinear, 1 },
{ sBspline, 2 },
{ sCostello, 2 },
{ sCostella, 2 },
{ sMitchell, 2 },
{ sCatmullRom, 2 },
{ sLanczos2, 2 },
@ -263,7 +97,175 @@ Image RescaleFilter(const Image& img, Size sz, const Rect& sr, int filter, Gate<
{ sLanczos5, 5 },
};
ASSERT(filter >= FILTER_NEAREST && filter <= FILTER_LANCZOS5);
return RescaleFilter(img, sz, sr, tab[filter].a, tab[filter].b, progress, co);
return tab[clamp(filter, 0, __countof(tab))];
}
void ImageFilterKernel::Init(double (*kfn)(double), int a, int src_sz, int tgt_sz)
{
this->a = a;
n = min(max(src_sz / tgt_sz, 1) * a, 31);
shift = 8 - n / 8;
mul = 1 << shift;
kernel_size = (2 * n) << shift;
ashift = a << shift;
INTERLOCKED {
static VectorMap<Tuple3<uintptr_t, int, int>, Buffer<int> > kache;
Tuple3<uintptr_t, int, int> key = MakeTuple((uintptr_t)kfn, a, shift);
Buffer<int> *k = kache.FindPtr(key);
if(k)
kernel = *k;
Buffer<int>& ktab = kache.GetAdd(key);
ktab.Alloc(((2 * a) << shift) + 1);
for(int i = 0; i < ((2 * a) << shift) + 1; i++)
ktab[i] = int((1 << shift) * (*kfn)((double)i / (1 << shift) - a));
kernel = ktab;
}
}
ImageFilterKernel::ImageFilterKernel(double (*kfn)(double), int a, int src_sz, int tgt_sz)
{
Init(kfn, a, src_sz, tgt_sz);
}
void ImageFilterKernel::Init(int filter, int src_sz, int tgt_sz)
{
auto t = GetImageFilterFunction(filter);
Init(t.a, t.b, src_sz, tgt_sz);
}
Image RescaleFilter(const Image& img, Size sz, const Rect& sr,
double (*kfn)(double x), int a,
Gate<int, int> progress, bool co)
{
ASSERT(Rect(img.GetSize()).Contains(sr));
Size isz = sr.GetSize();
if(isz.cx <= 0 || isz.cy <= 0 || sz.cx <= 0 || sz.cy <= 0)
return Image();
ImageFilterKernel kx(kfn, a, isz.cx, sz.cx);
ImageFilterKernel ky(kfn, a, isz.cy, sz.cy);
Buffer<int> px(sz.cx * 2 * kx.n * 2 * ky.n * 2);
int *xd = px;
Size cr = (Size(1 << kx.shift, 1 << ky.shift) -
Size(isz.cx << kx.shift, isz.cy << ky.shift) / sz) >> 1;
for(int x = 0; x < sz.cx; x++) {
int dx = ((x * isz.cx) << kx.shift) / sz.cx - cr.cx;
int sx = dx >> kx.shift;
dx -= sx << kx.shift;
if(dx < 0)
dx = 0;
for(int yy = -ky.n + 1; yy <= ky.n; yy++)
for(int xx = -kx.n + 1; xx <= kx.n; xx++) {
*xd++ = clamp(sx + xx, 0, isz.cx - 1) + sr.left;
*xd++ = kx.Get(xx, dx);
}
}
ImageBuffer ib(sz);
std::atomic<int> yy(0);
CoDo(co, [&] {
Buffer<int> py(2 * ky.n * 2);
for(int y = yy++; y < sz.cy; y = yy++) {
if(progress(y, sz.cy))
break;
int dy = ((y * isz.cy) << ky.shift) / sz.cy - cr.cy;
int sy = dy >> ky.shift;
dy -= sy << ky.shift;
if(dy < 0)
dy = 0;
int *xd = px;
int *yd = py;
for(int yy = -ky.n + 1; yy <= ky.n; yy++) {
*yd++ = ky.Get(yy, dy);
*yd++ = clamp(sy + yy, 0, isz.cy - 1) + sr.top;
}
RGBA *t = ib[y];
#ifdef CPU_SIMD
for(int x = 0; x < sz.cx; x++) {
f32x4 rgbaf = 0;
f32x4 w = 0;
yd = py;
for(int yy = 2 * ky.n; yy-- > 0;) {
int ky = *yd++;
const RGBA *l = img[*yd++];
for(int xx = 2 * kx.n; xx-- > 0;) {
f32x4 s = LoadRGBAF(&l[*xd++]);
f32x4 weight = f32all(float(ky * *xd++));
rgbaf += weight * s;
w += weight;
}
}
StoreRGBAF(t++, ClampRGBAF(rgbaf / w));
}
#else
for(int x = 0; x < sz.cx; x++) {
int red = 0;
int green = 0;
int blue = 0;
int alpha = 0;
int w = 0;
yd = py;
int hasalpha = 0;
for(int yy = 2 * ky.n; yy-- > 0;) {
int ky = *yd++;
const RGBA *l = img[*yd++];
for(int xx = 2 * kx.n; xx-- > 0;) {
const RGBA& s = l[*xd++];
int weight = ky * *xd++;
red += weight * s.r;
green += weight * s.g;
blue += weight * s.b;
alpha += weight * s.a;
hasalpha |= s.a - 255;
w += weight;
}
}
if(w)
if(hasalpha) {
t->a = alpha = Saturate255(alpha / w);
t->r = clamp(red / w, 0, alpha);
t->g = clamp(green / w, 0, alpha);
t->b = clamp(blue / w, 0, alpha);
}
else {
t->a = 255;
t->r = Saturate255(red / w);
t->g = Saturate255(green / w);
t->b = Saturate255(blue / w);
}
else
t->a = t->r = t->g = t->b = 0;
t++;
}
#endif
}
});
ib.SetResolution(img.GetResolution());
return ib;
}
Image RescaleFilter(const Image& img, Size sz,
double (*kfn)(double x), int a,
Gate<int, int> progress, bool co)
{
return RescaleFilter(img, sz, img.GetSize(), kfn, a, progress, co);
}
Image RescaleFilter(const Image& img, int cx, int cy,
double (*kfn)(double x), int a,
Gate<int, int> progress, bool co)
{
return RescaleFilter(img, Size(cx, cy), img.GetSize(), kfn, a, progress, co);
}
Image RescaleFilter(const Image& img, Size sz, const Rect& sr, int filter, Gate<int, int> progress, bool co)
{
if(IsNull(filter))
return Rescale(img, sz, sr);
auto t = GetImageFilterFunction(filter);
return RescaleFilter(img, sz, sr, t.a, t.b, progress, co);
}
Image RescaleFilter(const Image& img, Size sz, int filter, Gate<int, int> progress)
@ -291,7 +293,6 @@ Image CoRescaleFilter(const Image& img, int cx, int cy, int filter, Gate<int, in
return CoRescaleFilter(img, Size(cx, cy), filter, progress);
}
// Obsolete functions
Image RescaleBicubic(const Image& img, Size sz, const Rect& sr, Gate<int, int> progress)

View file

@ -10,7 +10,7 @@ RGBA Mul8(const RGBA& s, int mul)
}
struct SpanSource {
virtual void Get(RGBA *span, int x, int y, unsigned len) = 0;
virtual void Get(RGBA *span, int x, int y, unsigned len) const = 0;
virtual ~SpanSource() {}
};
@ -94,6 +94,7 @@ protected:
virtual void DashOp(const String& dash, double start);
virtual void DashOp(const Vector<double>& dash, double start);
virtual void InvertOp(bool invert);
virtual void ImageFilterOp(int filter);
virtual void TransformOp(const Xform2D& m);
@ -105,7 +106,7 @@ protected:
private:
enum {
MOVE, LINE, QUADRATIC, CUBIC, CHAR
MOVE, LINE, QUADRATIC, CUBIC, CHAR, CLEAR
};
struct LinearData {
int type;
@ -155,8 +156,9 @@ private:
bool hasclip;
bool mask;
bool onpath;
int filter = FILTER_BILINEAR;
};
PainterTarget *alt = NULL;
double alt_tolerance = Null;
ImageBuffer dummy;
@ -167,15 +169,18 @@ private:
int render_cx;
int dopreclip = 0;
Sizef size = Sizef(0, 0); // = ib.GetSize()
Buffer<byte> co_clear; // do lazy Clear
RGBA co_clear_color;
Attr attr;
Array<Attr> attrstack;
Attr attr;
Array<Attr> attrstack;
Vector<Buffer<ClippingLine>> clip;
Array< ImageBuffer > mask;
Vector<Vector<PathLine>> onpathstack;
Vector<double> pathlenstack;
int mtx_serial = 0;
ArrayMap<String, DashInfo> dashes;
Array<ImageBuffer> mask;
Vector<Vector<PathLine>> onpathstack;
Vector<double> pathlenstack;
int mtx_serial = 0;
ArrayMap<String, DashInfo> dashes;
Rectf preclip;
int preclip_mtx_serial = -1;
@ -187,7 +192,7 @@ private:
Pointf path_min, path_max;
};
enum { BATCH_SIZE = 128 }; // must be 2^n
enum { BATCH_SIZE = 256 }; // must be 2^n
Buffer<PathInfo> paths;
int path_index = 0;
@ -251,6 +256,9 @@ private:
double width;
double opacity;
Rasterizer rasterizer;
SpanSource *ss;
One<SpanSource> sso;
int alpha;
RGBA color;
RGBA c;
int subpath;
@ -274,7 +282,7 @@ private:
void DoPath0();
void DoPath() { if(IsNull(current)) DoPath0(); }
void ClearPath();
Buffer<ClippingLine> RenderPath(double width, Event<One<SpanSource>&> ss, const RGBA& color);
Buffer<ClippingLine> RenderPath(double width, One<SpanSource>& ss, const RGBA& color);
void RenderImage(double width, const Image& image, const Xform2D& transsrc,
dword flags);
void RenderRadial(double width, const Pointf& f, const RGBA& color1,
@ -319,5 +327,3 @@ public:
~BufferPainter() { Finish(); }
};
#include "Interpolator.hpp"

View file

@ -79,6 +79,13 @@ void BufferPainter::InvertOp(bool invert)
attr.invert = invert;
}
void BufferPainter::ImageFilterOp(int filter)
{
pathattr.filter = filter;
if(IsNull(current))
attr.invert = filter;
}
Vector<double> StringToDash(const String& dash, double& start);
void BufferPainter::DashOp(const String& dash, double start)
@ -149,6 +156,7 @@ void BufferPainter::ClearStopsOp()
void BufferPainter::Create(ImageBuffer& ib, int mode_)
{
ip = &ib;
ip->SetKind(IMAGE_ALPHA);
if(mode_ != mode || (Size)size != ib.GetSize()) {
mode = mode_;
@ -169,6 +177,8 @@ void BufferPainter::Create(ImageBuffer& ib, int mode_)
co_subpixel.Clear();
co_span.Clear();
span.Clear();
co_clear.Clear();
}
SyncCo();

View file

@ -458,4 +458,4 @@ void NoAAFillerFilter::Render(int val)
t->Render(val < 128 ? 0 : 256);
}
}
}

View file

@ -7,10 +7,11 @@ namespace Upp {
force_inline
int IntAndFraction(f32x4 x, f32x4& fraction)
{
x = x + f32all(8000); // Truncate truncates toward 0, need to fix negatives
const int ishift = 1000000;
x = x + f32all(ishift); // Truncate truncates toward 0, need to fix negatives
i32x4 m = Truncate(x);
fraction = x - ToFloat(m);
return (int)m - 8000;
return (int)m - ishift;
}
force_inline
@ -19,7 +20,9 @@ int Int(f32x4 x)
return (int)Truncate(x + f32all(8000)) - 8000;
}
struct PainterImageSpanData {
#endif
struct PainterImageSpan : SpanSource {
int ax, ay, cx, cy, maxx, maxy;
byte style;
byte hstyle, vstyle;
@ -27,12 +30,36 @@ struct PainterImageSpanData {
bool fixed;
Image image;
Xform2D xform;
bool dofilter = false;
ImageFilterKernel kx, ky;
PainterImageSpanData(dword flags, const Xform2D& m, const Image& img, bool co, bool imagecache) {
struct RGBAF {
double r, g, b, a;
void Put(double weight, const RGBA& src) {
r += weight * src.r;
g += weight * src.g;
b += weight * src.b;
a += weight * src.a;
}
RGBA Get(double div) const {
RGBA c;
c.r = Saturate255(int(r / div));
c.g = Saturate255(int(g / div));
c.b = Saturate255(int(b / div));
c.a = Saturate255(int(a / div));
return c;
}
RGBAF() { r = g = b = a = 0; }
};
PainterImageSpan(dword flags, const Xform2D& m, const Image& img, bool co, bool imagecache, int filter) {
style = byte(flags & 15);
hstyle = byte(flags & 3);
vstyle = byte(flags & 12);
fast = flags & FILL_FAST;
fast = (flags & FILL_FAST) || filter == FILTER_NEAREST;
image = img;
int nx = 1;
int ny = 1;
@ -42,12 +69,19 @@ struct PainterImageSpanData {
nx = (int)max(1.0, 1.0 / sc.x);
ny = (int)max(1.0, 1.0 / sc.y);
}
if(filter != FILTER_BILINEAR) {
kx.Init(filter, nx, 1);
ky.Init(filter, ny, 1);
dofilter = true;
fast = false;
}
}
if(nx == 1 && ny == 1)
if(nx == 1 && ny == 1 || dofilter)
xform = Inverse(m);
else {
if(!fast)
image = (imagecache ? MinifyCached : Minify)(image, nx, ny, co);
image = (imagecache ? MinifyCached : Minify)(image, nx, ny, nx * ny > 20000 && co);
xform = Inverse(m) * Xform2D::Scale(1.0 / nx, 1.0 / ny);
}
cx = image.GetWidth();
@ -56,19 +90,12 @@ struct PainterImageSpanData {
maxy = cy - 1;
ax = 6000000 / cx * cx * 2;
ay = 6000000 / cy * cy * 2;
fixed = hstyle && vstyle;
}
PainterImageSpanData() {}
};
const RGBA *Pixel(int x, int y) const { return &image[y][x]; }
struct PainterImageSpan : SpanSource, PainterImageSpanData {
PainterImageSpan(const PainterImageSpanData& f)
: PainterImageSpanData(f) {}
const RGBA *Pixel(int x, int y) { return &image[y][x]; }
const RGBA *GetPixel(int x, int y) {
const RGBA *GetPixel(int x, int y) const {
if(hstyle == FILL_HPAD)
x = minmax(x, 0, maxx);
else
@ -89,13 +116,65 @@ struct PainterImageSpan : SpanSource, PainterImageSpanData {
return fixed || (x >= 0 && x < cx && y >= 0 && y < cy) ? &image[y][x] : &zero;
}
virtual void Get(RGBA *span, int x, int y, unsigned len)
void GetFilter(RGBA *span, Pointf p0, Pointf dd, unsigned len) const
{
int ii = 0;
while(len--) {
const int ishift = 1000000; // to avoid problems with negatives
Pointf p = (p0 + ii++ * dd) + Pointf(ishift, ishift);
Point l = p;
Pointf h = p - Pointf(l);
l -= Point(ishift, ishift);
int mx = int(kx.mul * h.x);
int my = int(ky.mul * h.y);
#ifdef CPU_SIMD
f32x4 rgbaf = 0;
f32x4 w = 0;
for(int yy = -ky.n + 1; yy <= ky.n; yy++) {
int wy = ky.Get(yy, my);
for(int xx = -kx.n + 1; xx <= kx.n; xx++) {
f32x4 s = LoadRGBAF(GetPixel(l.x + xx, l.y + yy));
f32x4 weight = f32all(wy * kx.Get(xx, mx));
rgbaf += weight * s;
w += weight;
}
}
StoreRGBAF(span++, ClampRGBAF(rgbaf / w));
#else
RGBAF rgbaf;
double w = 0;
for(int yy = -ky.n + 1; yy <= ky.n; yy++) {
int wy = ky.Get(yy, my);
for(int xx = -kx.n + 1; xx <= kx.n; xx++) {
double weight = wy * kx.Get(xx, mx);
rgbaf.Put(weight, *GetPixel(l.x + xx, l.y + yy));
w += weight;
}
}
*span++ = rgbaf.Get(w);
#endif
}
}
virtual void Get(RGBA *span, int x, int y, unsigned len) const
{
PAINTER_TIMING("ImageSpan::Get");
Pointf p0 = xform.Transform(Pointf(x, y));
Pointf dd = xform.Transform(Pointf(x + 1, y)) - p0;
Pointf p0, dd;
if(fast) {
p0 = xform.Transform(Pointf(x, y));
dd = xform.Transform(Pointf(x + 1, y)) - p0;
}
else {
p0 = xform.Transform(Pointf(x, y) + Pointf(0.5, 0.5)) - Pointf(0.5, 0.5);
dd = xform.Transform(Pointf(x + 1, y) + Pointf(0.5, 0.5)) - Pointf(0.5, 0.5) - p0;
}
if(dofilter)
return GetFilter(span, p0, dd, len);
#ifdef CPU_SIMD
f32x4 x0 = f32all(p0.x);
f32x4 y0 = f32all(p0.y);
f32x4 dx = f32all(dd.x);
@ -110,7 +189,6 @@ struct PainterImageSpan : SpanSource, PainterImageSpanData {
ii += v1;
};
fixed = hstyle && vstyle;
if(hstyle + vstyle == 0 && fast) {
while(len--) {
GetIXY();
@ -162,141 +240,42 @@ struct PainterImageSpan : SpanSource, PainterImageSpanData {
p11 = p11 * fy;
p10 = p10 * fx;
p11 = p11 * fx;
fx = v1 - fx;
fy = v1 - fy;
p00 = p00 * fy;
p10 = p10 * fy;
p00 = p00 * fx;
p01 = p01 * fx;
StoreRGBAF(span, p00 + p01 + p10 + p11);
}
++span;
}
}
};
void BufferPainter::RenderImage(double width, const Image& image, const Xform2D& transsrc, dword flags)
{
current = Null;
if(image.GetWidth() == 0 || image.GetHeight() == 0)
return;
PainterImageSpanData f(flags, transsrc * pathattr.mtx, image, co, imagecache);
RenderPath(width, [&](One<SpanSource>& s) {
s.Create<PainterImageSpan>(f);
}, RGBAZero());
}
void BufferPainter::FillOp(const Image& image, const Xform2D& transsrc, dword flags)
{
Close();
RenderImage(-1, image, transsrc, flags);
}
void BufferPainter::StrokeOp(double width, const Image& image, const Xform2D& transsrc, dword flags)
{
RenderImage(width, image, transsrc, flags);
}
#else
struct PainterImageSpanData {
int ax, ay, cx, cy, maxx, maxy;
byte style;
byte hstyle, vstyle;
bool fast;
bool fixed;
Image image;
Xform2D xform;
PainterImageSpanData(dword flags, const Xform2D& m, const Image& img, bool co, bool imagecache) {
style = byte(flags & 15);
hstyle = byte(flags & 3);
vstyle = byte(flags & 12);
fast = flags & FILL_FAST;
image = img;
int nx = 1;
int ny = 1;
if(!fast) {
Pointf sc = m.GetScaleXY();
if(sc.x >= 0.01 && sc.y >= 0.01) {
nx = (int)max(1.0, 1.0 / sc.x);
ny = (int)max(1.0, 1.0 / sc.y);
}
}
if(nx == 1 && ny == 1)
xform = Inverse(m);
else {
if(!fast)
image = (imagecache ? MinifyCached : Minify)(image, nx, ny, co);
xform = Inverse(m) * Xform2D::Scale(1.0 / nx, 1.0 / ny);
}
cx = image.GetWidth();
cy = image.GetHeight();
maxx = cx - 1;
maxy = cy - 1;
ax = 6000000 / cx * cx * 2;
ay = 6000000 / cy * cy * 2;
}
PainterImageSpanData() {}
};
struct PainterImageSpan : SpanSource, PainterImageSpanData {
LinearInterpolator interpolator;
PainterImageSpan(const PainterImageSpanData& f)
: PainterImageSpanData(f) {
interpolator.Set(xform);
}
RGBA Pixel(int x, int y) { return image[y][x]; }
RGBA GetPixel(int x, int y) {
if(hstyle == FILL_HPAD)
x = minmax(x, 0, maxx);
else
if(hstyle == FILL_HREFLECT)
x = (x + ax) / cx & 1 ? (ax - x - 1) % cx : (x + ax) % cx;
else
if(hstyle == FILL_HREPEAT)
x = (x + ax) % cx;
if(vstyle == FILL_VPAD)
y = minmax(y, 0, maxy);
else
if(vstyle == FILL_VREFLECT)
y = (y + ay) / cy & 1 ? (ay - y - 1) % cy : (y + ay) % cy;
else
if(vstyle == FILL_VREPEAT)
y = (y + ay) % cy;
return fixed || (x >= 0 && x < cx && y >= 0 && y < cy) ? image[y][x] : RGBAZero();
}
virtual void Get(RGBA *span, int x, int y, unsigned len)
{
PAINTER_TIMING("ImageSpan::Get");
interpolator.Begin(x, y, len);
fixed = hstyle && vstyle;
int ii = 0;
if(hstyle + vstyle == 0 && fast) {
while(len--) {
Point l = interpolator.Get() >> 8;
Pointf p = p0 + ii++ * dd;
Point l = p;
if(l.x > 0 && l.x < maxx && l.y > 0 && l.y < maxy)
*span = Pixel(l.x, l.y);
*span = *Pixel(l.x, l.y);
else
if(style == 0 && (l.x < -1 || l.x > cx || l.y < -1 || l.y > cy))
*span = RGBAZero();
else
*span = GetPixel(l.x, l.y);
*span = *GetPixel(l.x, l.y);
++span;
}
return;
}
while(len--) {
Point h = interpolator.Get();
Point l = h >> 8;
const int ishift = 1000000; // to avoid problems with negatives
Pointf p = (p0 + ii++ * dd) + Pointf(ishift, ishift);
Point l = p;
Point h = Pointf(256 * (p - Pointf(l)));
l -= Point(ishift, ishift);
if(hstyle == FILL_HREPEAT)
l.x = (l.x + ax) % cx;
if(vstyle == FILL_VREPEAT)
@ -306,27 +285,25 @@ struct PainterImageSpan : SpanSource, PainterImageSpanData {
else
if(fast) {
if(l.x > 0 && l.x < maxx && l.y > 0 && l.y < maxy)
*span = Pixel(l.x, l.y);
*span = *Pixel(l.x, l.y);
else
*span = GetPixel(l.x, l.y);
*span = *GetPixel(l.x, l.y);
}
else {
RGBAV v;
v.Set(0);
h.x &= 255;
h.y &= 255;
Point u = -h + 256;
if(l.x > 0 && l.x < maxx && l.y > 0 && l.y < maxy) {
v.Put(u.x * u.y, Pixel(l.x, l.y));
v.Put(h.x * u.y, Pixel(l.x + 1, l.y));
v.Put(u.x * h.y, Pixel(l.x, l.y + 1));
v.Put(h.x * h.y, Pixel(l.x + 1, l.y + 1));
v.Put(u.x * u.y, *Pixel(l.x, l.y));
v.Put(h.x * u.y, *Pixel(l.x + 1, l.y));
v.Put(u.x * h.y, *Pixel(l.x, l.y + 1));
v.Put(h.x * h.y, *Pixel(l.x + 1, l.y + 1));
}
else {
v.Put(u.x * u.y, GetPixel(l.x, l.y));
v.Put(h.x * u.y, GetPixel(l.x + 1, l.y));
v.Put(u.x * h.y, GetPixel(l.x, l.y + 1));
v.Put(h.x * h.y, GetPixel(l.x + 1, l.y + 1));
v.Put(u.x * u.y, *GetPixel(l.x, l.y));
v.Put(h.x * u.y, *GetPixel(l.x + 1, l.y));
v.Put(u.x * h.y, *GetPixel(l.x, l.y + 1));
v.Put(h.x * h.y, *GetPixel(l.x + 1, l.y + 1));
}
span->r = byte(v.r >> 16);
span->g = byte(v.g >> 16);
@ -335,6 +312,7 @@ struct PainterImageSpan : SpanSource, PainterImageSpanData {
}
++span;
}
#endif
}
};
@ -343,10 +321,9 @@ void BufferPainter::RenderImage(double width, const Image& image, const Xform2D&
current = Null;
if(image.GetWidth() == 0 || image.GetHeight() == 0)
return;
PainterImageSpanData f(flags, transsrc * pathattr.mtx, image, co, imagecache);
RenderPath(width, [&](One<SpanSource>& s) {
s.Create<PainterImageSpan>(f);
}, RGBAZero());
One<SpanSource> ss;
ss.Create<PainterImageSpan>(flags, transsrc * pathattr.mtx, image, co, imagecache, pathattr.filter);
RenderPath(width, ss, RGBAZero());
}
void BufferPainter::FillOp(const Image& image, const Xform2D& transsrc, dword flags)
@ -360,6 +337,4 @@ void BufferPainter::StrokeOp(double width, const Image& image, const Xform2D& tr
RenderImage(width, image, transsrc, flags);
}
#endif
}
}

View file

@ -1,43 +0,0 @@
inline
void LinearInterpolator::Dda2::Set(int p1, int p2, int len)
{
count = len <= 0 ? 1 : len;
lift = (p2 - p1) / count;
rem = (p2 - p1) % count;
mod = rem;
p = p1;
if(mod <= 0) {
mod += count;
rem += count;
lift--;
}
mod -= count;
}
inline
int LinearInterpolator::Dda2::Get()
{
int pp = p;
mod += rem;
p += lift;
if(mod > 0) {
mod -= count;
p++;
}
return pp;
}
inline
void LinearInterpolator::Begin(int x, int y, int len)
{
Pointf p1 = xform.Transform(Pointf(x, y));
Pointf p2 = xform.Transform(Pointf(x + len, y));
ddax.Set(Q8(p1.x), Q8(p2.x), len);
dday.Set(Q8(p1.y), Q8(p2.y), len);
}
inline
Point LinearInterpolator::Get()
{
return Point(ddax.Get(), dday.Get());
}

View file

@ -160,24 +160,4 @@ public:
~Rasterizer() { Free(); }
};
class LinearInterpolator {
struct Dda2 {
int count, lift, rem, mod, p;
void Set(int a, int b, int len);
int Get();
};
Xform2D xform;
Dda2 ddax, dday;
static int Q8(double x) { return int(256 * x + 0.5); }
public:
void Set(const Xform2D& m) { xform = m; }
void Begin(int x, int y, int len);
Point Get();
};
void ApproximateChar(LinearPathConsumer& t, Pointf at, int ch, Font fnt, double tolerance);

View file

@ -4,8 +4,9 @@ namespace Upp {
void BufferPainter::BeginOnPathOp(double q, bool abs)
{
One<SpanSource> none;
if(onpath.GetCount() == 0)
RenderPath(ONPATH, Null, RGBAZero());
RenderPath(ONPATH, none, RGBAZero());
Begin();
if(pathlen > 0) {
if(!abs)

View file

@ -245,6 +245,9 @@ void Painter::Paint(const Painting& pic)
case PAINTING_INVERT:
InvertOp(ss.Get());
break;
case PAINTING_IMAGE_FILTER:
ImageFilterOp(ss.Get());
break;
case PAINTING_DASH:
{
n = ss.Get32();

View file

@ -47,12 +47,14 @@ void PaintCharacter(Painter& sw, const Pointf& p, int chr, Font font)
sw.EvenOdd(true);
}
Xform2D GetLineSzXform(const Pointf& p1, const Pointf& p2, Pointf p3, const Sizef& sz)
{
return Xform2D::Map(Pointf(0, 0), Pointf(sz.cx, 0), Pointf(sz.cx, sz.cy), p1, p2, p3);
}
Xform2D GetLineSzXform(const Pointf& p1, const Pointf& p2, const Sizef& sz)
{
Xform2D m = Xform2D::Scale(Distance(p1, p2) / sz.cx);
m = m * Xform2D::Rotation(Bearing(p2 - p1));
m = m * Xform2D::Translation(p1.x, p1.y);
return m;
return GetLineSzXform(p1, p2, Pointf(p2.x, p2.y + Distance(p1, p2) / sz.cx * sz.cy), sz);
}
Painter& Painter::Fill(const Image& image, Pointf p1, Pointf p2, dword flags)
@ -344,6 +346,7 @@ void NilPainter::LineJoinOp(int linejoin) {}
void NilPainter::MiterLimitOp(double l) {}
void NilPainter::EvenOddOp(bool evenodd) {}
void NilPainter::InvertOp(bool invert) {}
void NilPainter::ImageFilterOp(int filter) {}
void NilPainter::DashOp(const Vector<double>& dash, double start) {}
void NilPainter::TransformOp(const Xform2D& m) {}
void NilPainter::BeginOp() {}

View file

@ -154,6 +154,7 @@ protected:
virtual void DashOp(const Vector<double>& dash, double start = 0) = 0;
virtual void DashOp(const String& dash, double start = 0);
virtual void InvertOp(bool invert) = 0;
virtual void ImageFilterOp(int filter) = 0;
virtual void TransformOp(const Xform2D& m) = 0;
@ -322,6 +323,7 @@ public:
Painter& Dash(const Vector<double>& dash, double start);
Painter& Dash(const char *dash, double start = 0);
Painter& Invert(bool b = true);
Painter& ImageFilter(int filter);
Painter& Transform(const Xform2D& m);
Painter& Translate(double x, double y);
@ -438,6 +440,7 @@ protected:
virtual void EvenOddOp(bool evenodd);
virtual void DashOp(const Vector<double>& dash, double start);
virtual void InvertOp(bool invert);
virtual void ImageFilterOp(int filter);
virtual void TransformOp(const Xform2D& m);

View file

@ -185,6 +185,13 @@ inline Painter& Painter::Invert(bool b)
return *this;
}
inline
Painter& Painter::ImageFilter(int filter)
{
ImageFilterOp(filter);
return *this;
}
inline Painter& Painter::Dash(const Vector<double>& dash, double start)
{
if(dash.GetCount() & 1) {

View file

@ -15,7 +15,6 @@ file
PaintPainting.cpp,
PainterInit.icpp,
LinearPath.h,
Interpolator.hpp,
Xform2D.cpp,
Approximate.cpp,
Stroker.cpp,

View file

@ -275,6 +275,12 @@ void PaintingPainter::InvertOp(bool invert)
Put(invert);
}
void PaintingPainter::ImageFilterOp(int filter)
{
Put(PAINTING_IMAGE_FILTER);
Put(filter);
}
void PaintingPainter::DashOp(const Vector<double>& dash, double start)
{
Put(PAINTING_DASH);

View file

@ -57,6 +57,8 @@ enum {
PAINTING_FILL_RADIAL_X,
PAINTING_STROKE_RADIAL_X,
PAINTING_IMAGE_FILTER,
};
class PaintingPainter : public Painter {
@ -131,6 +133,7 @@ protected:
virtual void EvenOddOp(bool evenodd);
virtual void DashOp(const Vector<double>& dash, double start);
virtual void InvertOp(bool invert);
virtual void ImageFilterOp(int filter);
virtual void TransformOp(const Xform2D& m);

View file

@ -12,8 +12,14 @@ void BufferPainter::ClearPath()
path_info->path_min = Pointf(1e200, 1e200);
path_info->path_max = -Pointf(1e200, 1e200);
path_info->path.SetCount(1);
path_info->path.Top().Clear();
path_info->path.Top().Reserve(128);
Vector<byte>& p = path_info->path.Top();
if(path_info->path.Top().GetCount() > 2048) {
p.Clear();
p.Reserve(1024);
}
else
p.SetCount(0);
}
void BufferPainter::DoPath0()
@ -53,6 +59,21 @@ template <class T> T& BufferPainter::PathAdd(int type)
return *e;
}
void BufferPainter::ClearOp(const RGBA& color)
{
Finish();
if(co && mode != MODE_NOAA) { // schedule for late clear during rendering
if(!co_clear)
co_clear.Alloc(ip->GetHeight());
memset(~co_clear, 1, ip->GetHeight());
co_clear_color = color;
}
else
UPP::Fill(~*ip, color, ip->GetLength());
if(color.a == 255)
ip->SetKind(IMAGE_OPAQUE);
}
void BufferPainter::MoveOp(const Pointf& p, bool rel)
{
LLOG("@ MoveOp " << p << ", " << rel);
@ -60,6 +81,7 @@ void BufferPainter::MoveOp(const Pointf& p, bool rel)
PathAdd<LinearData>(MOVE).p = move;
}
force_inline
void BufferPainter::DoMove0()
{
if(IsNull(move))
@ -69,7 +91,6 @@ void BufferPainter::DoMove0()
void BufferPainter::LineOp(const Pointf& p, bool rel)
{
DoMove0();
LinearData h;
PathAdd<LinearData>(LINE).p = ccontrol = qcontrol = EndPoint(p, rel);
}

View file

@ -3,7 +3,7 @@
namespace Upp {
struct PainterRadialSpan : SpanSource {
LinearInterpolator interpolator;
Xform2D im;
double cx, cy, r, fx, fy;
int style;
int alpha;
@ -22,18 +22,19 @@ struct PainterRadialSpan : SpanSource {
C = fx * fx + fy * fy - r * r;
}
void Get(RGBA *_span, int x, int y, unsigned len)
void Get(RGBA *_span, int x, int y, unsigned len) const
{
if(r <= 0)
return;
interpolator.Begin(x, y, len);
Pointf p0 = im.Transform(Pointf(x, y));
Pointf dd = im.Transform(Pointf(x + 1, y)) - p0;
RGBA *span = (RGBA *)_span;
int ii = 0;
while(len--) {
Point p = interpolator.Get();
Pointf p = p0 + dd * ii++;
double dx = p.x - cx - fx;
double dy = p.y - cy - fy;
int h;
const double q256 = 1 / 256.0;
double dx = q256 * p.x - cx - fx;
double dy = q256 * p.y - cy - fy;
if(dx == 0 && dy == 0)
h = 0;
else {
@ -59,13 +60,13 @@ void BufferPainter::RenderRadial(double width, const Pointf& f, const RGBA& colo
const Xform2D& m, int style)
{
Image gradient = Gradient(color1, color2, 2048);
RenderPath(width, [=](One<SpanSource>& ss) {
PainterRadialSpan& sg = ss.Create<PainterRadialSpan>();
sg.interpolator.Set(Inverse(m));
sg.style = style;
sg.Set(c.x, c.y, r, f.x, f.y);
sg.gradient = gradient[0];
}, RGBAZero());
One<SpanSource> ss;
PainterRadialSpan& sg = ss.Create<PainterRadialSpan>();
sg.im = Inverse(m);
sg.style = style;
sg.Set(c.x, c.y, r, f.x, f.y);
sg.gradient = gradient[0];
RenderPath(width, ss, RGBAZero());
}
void BufferPainter::FillOp(const Pointf& f, const RGBA& color1, const Pointf& c, double r, const RGBA& color2, int style)

View file

@ -37,7 +37,7 @@ void Rasterizer::Create(int cx, int cy, bool subpixel)
cell.Alloc(sz.cy + 1); // one more for overrun
STATIC_ASSERT(sizeof(CellArray) == 128);
// STATIC_ASSERT(sizeof(CellArray) == 256);
cliprect = Sizef(sz);
Init();
@ -46,10 +46,20 @@ void Rasterizer::Create(int cx, int cy, bool subpixel)
void Rasterizer::Free()
{
if(cell)
for(int i = 0; i <= sz.cy; i++)
if(cell[i].alloc != SVO_ALLOC)
if(cell) {
for(int i = min_y; i <= max_y; i++) {
if(cell[i].alloc != SVO_ALLOC) {
MemoryFree(cell[i].ptr);
cell[i].alloc = SVO_ALLOC;
}
cell[i].count = 0;
}
if(cell[sz.cy].alloc != SVO_ALLOC) { // check overrun
MemoryFree(cell[sz.cy].ptr);
cell[sz.cy].alloc = SVO_ALLOC;
}
cell[sz.cy].count = 0;
}
}
void Rasterizer::Init()
@ -61,13 +71,7 @@ void Rasterizer::Init()
void Rasterizer::Reset()
{
for(int i = min_y; i <= max_y; i++) {
if(cell[i].alloc != SVO_ALLOC) {
MemoryFree(cell[i].ptr);
cell[i].alloc = SVO_ALLOC;
}
cell[i].count = 0;
}
Free();
Init();
}

View file

@ -8,19 +8,6 @@ namespace Upp {
void PainterTarget::Fill(double width, SpanSource *ss, const RGBA& color) {}
void BufferPainter::ClearOp(const RGBA& color)
{
Finish();
if(co) {
CoFor(ip->GetHeight(), [&](int i) {
UPP::Fill((*ip)[i], color, ip->GetWidth());
});
}
else
UPP::Fill(~*ip, color, ip->GetLength());
ip->SetKind(color.a == 255 ? IMAGE_OPAQUE : IMAGE_ALPHA);
}
BufferPainter::PathJob::PathJob(Rasterizer& rasterizer, double width, const PathInfo *path_info,
const SimpleAttr& attr, const Rectf& preclip, bool isregular)
: trans(attr.mtx)
@ -144,7 +131,7 @@ void BufferPainter::SyncCo()
}
}
Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSource>&> ss, const RGBA& color)
Buffer<ClippingLine> BufferPainter::RenderPath(double width, One<SpanSource>& ss, const RGBA& color)
{
PAINTER_TIMING("RenderPath");
Buffer<ClippingLine> newclip;
@ -185,7 +172,14 @@ Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSourc
}
if(co) {
if(width >= FILL && !ss && !alt && findarg(mode, MODE_ANTIALIASED, MODE_SUBPIXEL) >= 0) {
if(ss && !co_span) {
int n = CoWork::GetPoolSize();
co_span.Alloc(n);
for(int i = 0; i < n; i++)
co_span[i].Alloc((subpixel ? 3 : 1) * ip->GetWidth() + 3);
}
if(width >= FILL && !alt && findarg(mode, MODE_ANTIALIASED, MODE_SUBPIXEL) >= 0) {
for(int i = 0; i < path_info->path.GetCount(); i++) {
while(jobcount >= cojob.GetCount())
cojob.Add().rasterizer.Create(ip->GetWidth(), ip->GetHeight(), mode == MODE_SUBPIXEL);
@ -197,9 +191,12 @@ Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSourc
job.color = color;
job.preclip = preclip;
job.regular = regular;
job.ss = ~ss;
if(i + 1 == path_info->path.GetCount()) // last subpath
job.sso = pick(ss); // transfer SpanSource ownership to last subpath
if(jobcount + emptycount >= BATCH_SIZE)
FinishPathJob();
}
if(jobcount + emptycount >= BATCH_SIZE)
FinishPathJob();
return newclip;
}
@ -213,13 +210,6 @@ Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSourc
if(j.preclipped)
return newclip;
if(co && ss && !co_span) {
int n = CoWork::GetPoolSize();
co_span.Alloc(n);
for(int i = 0; i < n; i++)
co_span[i].Alloc((subpixel ? 3 : 1) * ip->GetWidth() + 3);
}
bool doclip = width == CLIP;
auto fill = [&](CoWork *co) {
int opacity = int(256 * pathattr.opacity);
@ -232,7 +222,6 @@ Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSourc
ClipFiller clip_filler;
NoAAFillerFilter noaa_filler;
MaskFillerFilter mf;
One<SpanSource> rss;
if(subpixel) {
int ci = CoWork::GetWorkerIndex();
@ -246,7 +235,6 @@ Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSourc
}
else
if(ss) {
ss(rss);
RGBA *lspan;
int ci = CoWork::GetWorkerIndex();
if(co && ci >= 0)
@ -257,13 +245,13 @@ Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSourc
lspan = span;
}
if(subpixel) {
subpixel_filler.ss = ~rss;
subpixel_filler.ss = ~ss;
subpixel_filler.buffer = lspan;
subpixel_filler.alpha = opacity;
rg = &subpixel_filler;
}
else {
span_filler.ss = ~rss;
span_filler.ss = ~ss;
span_filler.buffer = lspan;
span_filler.alpha = opacity;
rg = &span_filler;
@ -271,7 +259,7 @@ Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSourc
}
else {
if(subpixel) {
subpixel_filler.ss = NULL;
subpixel_filler.ss = nullptr;
subpixel_filler.color = Mul8(color, opacity);
subpixel_filler.invert = pathattr.invert;
rg = &subpixel_filler;
@ -288,7 +276,7 @@ Buffer<ClippingLine> BufferPainter::RenderPath(double width, Event<One<SpanSourc
}
if(width != ONPATH) {
if(alt)
alt->Fill(width, ~rss, color);
alt->Fill(width, ~ss, color);
else {
PAINTER_TIMING("Fill");
int ii = 0;
@ -347,116 +335,156 @@ void BufferPainter::FinishPathJob()
{
if(jobcount == 0)
return;
CoWork co;
co * [&] {
for(;;) {
int i = co.Next();
if(i >= jobcount)
break;
CoJob& b = cojob[i];
b.rasterizer.Reset();
PathJob j(b.rasterizer, b.width, b.path_info, b.attr, b.preclip, b.regular);
if(!j.preclipped) {
b.evenodd = j.evenodd;
BufferPainter::RenderPathSegments(j.g, b.path_info->path[b.subpath], j.regular ? &b.attr : NULL, j.tolerance);
{
std::atomic<int> ii(0);
CoDo([&] {
for(int i = ii++; i < jobcount; i = ii++) {
CoJob& b = cojob[i];
b.rasterizer.Reset();
PathJob j(b.rasterizer, b.width, b.path_info, b.attr, b.preclip, b.regular);
if(!j.preclipped) {
b.evenodd = j.evenodd;
BufferPainter::RenderPathSegments(j.g, b.path_info->path[b.subpath], j.regular ? &b.attr : NULL, j.tolerance);
}
}
}
};
});
}
FinishFillJob();
FinishFillJob(); // finish running fill job (if any) so that we can start new one
fillcount = jobcount;
Swap(cofill, cojob); // Swap to keep allocated rasters (instead of pick)
fill_job & [=] {
int miny = ip->GetHeight() - 1;
int maxy = 0;
for(int i = 0; i < fillcount; i++) {
CoJob& j = cofill[i];
miny = min(miny, j.rasterizer.MinY());
maxy = max(maxy, j.rasterizer.MaxY());
j.c = Mul8(j.color, int(256 * j.attr.opacity));
int alpha = int(256 * j.attr.opacity);
if(!j.ss)
j.c = Mul8(j.color, alpha);
else
j.alpha = alpha;
}
auto fill = [&](int ymin, int ymax) {
auto fill = [&](int y) {
if(co_clear && co_clear[y]) {
UPP::Fill((*ip)[y], co_clear_color, ip->GetWidth());
co_clear[y] = false;
}
if(subpixel) {
SubpixelFiller subpixel_filler;
subpixel_filler.ss = NULL;
int ci = CoWork::GetWorkerIndex();
subpixel_filler.sbuffer = ci >= 0 ? co_subpixel[ci] : subpixel;
for(int i = 0; i < fillcount; i++) {
CoJob& j = cofill[i];
int jymin = max(j.rasterizer.MinY(), ymin);
int jymax = min(j.rasterizer.MaxY(), ymax);
for(int y = jymin; y <= jymax; y++)
if(j.rasterizer.NotEmpty(y)) {
subpixel_filler.color = j.c;
subpixel_filler.invert = j.attr.invert;
subpixel_filler.t = (*ip)[y];
subpixel_filler.end = subpixel_filler.t + ip->GetWidth();
if(clip.GetCount()) {
if(clip.Top()) {
MaskFillerFilter mf;
const ClippingLine& s = clip.Top()[y];
if(!s.IsEmpty() && !s.IsFull()) {
mf.Set(&subpixel_filler, s);
j.rasterizer.Render(y, mf, j.evenodd);
}
if(j.rasterizer.NotEmpty(y)) {
subpixel_filler.color = j.c;
subpixel_filler.ss = j.ss;
subpixel_filler.invert = j.attr.invert;
subpixel_filler.t = (*ip)[y];
subpixel_filler.end = subpixel_filler.t + ip->GetWidth();
if(j.ss) {
RGBA *lspan;
if(ci >= 0)
lspan = co_span[ci];
else {
if(!span)
span.Alloc(3 * ip->GetWidth() + 3);
lspan = span;
}
subpixel_filler.buffer = lspan;
subpixel_filler.alpha = j.alpha;
subpixel_filler.y = y;
}
if(clip.GetCount()) {
if(clip.Top()) {
MaskFillerFilter mf;
const ClippingLine& s = clip.Top()[y];
if(!s.IsEmpty() && !s.IsFull()) {
mf.Set(&subpixel_filler, s);
j.rasterizer.Render(y, mf, j.evenodd);
}
}
else
j.rasterizer.Render(y, subpixel_filler, j.evenodd);
}
else
j.rasterizer.Render(y, subpixel_filler, j.evenodd);
}
}
}
else {
SolidFiller solid_filler;
SpanFiller span_filler;
for(int i = 0; i < fillcount; i++) {
CoJob& j = cofill[i];
int jymin = max(j.rasterizer.MinY(), ymin);
int jymax = min(j.rasterizer.MaxY(), ymax);
for(int y = jymin; y <= jymax; y++)
if(j.rasterizer.NotEmpty(y)) {
if(j.rasterizer.NotEmpty(y)) {
Rasterizer::Filler *rg;
if(j.ss) {
RGBA *lspan;
int ci = CoWork::GetWorkerIndex();
if(ci >= 0)
lspan = co_span[ci];
else {
if(!span)
span.Alloc(ip->GetWidth() + 3);
lspan = span;
}
span_filler.ss = j.ss;
span_filler.buffer = lspan;
span_filler.alpha = j.alpha;
span_filler.y = y;
span_filler.t = (*ip)[y];
rg = &span_filler;
}
else {
solid_filler.c = j.c;
solid_filler.invert = j.attr.invert;
solid_filler.t = (*ip)[y];
if(clip.GetCount()) {
if(clip.Top()) {
MaskFillerFilter mf;
const ClippingLine& s = clip.Top()[y];
if(!s.IsEmpty() && !s.IsFull()) {
mf.Set(&solid_filler, s);
j.rasterizer.Render(y, mf, j.evenodd);
}
rg = &solid_filler;
}
if(clip.GetCount()) {
if(clip.Top()) {
MaskFillerFilter mf;
const ClippingLine& s = clip.Top()[y];
if(!s.IsEmpty() && !s.IsFull()) {
mf.Set(rg, s);
j.rasterizer.Render(y, mf, j.evenodd);
}
}
else
j.rasterizer.Render(y, solid_filler, j.evenodd);
}
else
j.rasterizer.Render(y, *rg, j.evenodd);
}
}
}
};
int n = maxy - miny;
if(n >= 0) {
if(n > 6) {
CoWork co;
co * [&] {
if(maxy >= miny) {
if(maxy - miny > 3) {
std::atomic<int> ii(0);
CoDo([&] {
for(;;) {
const int N = 4;
int y = N * co.Next() + miny;
int y = ii++ + miny;
if(y > maxy)
break;
fill(y, min(y + N - 1, maxy));
fill(y);
}
};
});
}
else
fill(miny, maxy);
for(int y = miny; y <= maxy; y++)
fill(y);
}
for(int i = 0; i < fillcount; i++) // we can release SpanSources now
cofill[i].sso.Clear();
};
jobcount = emptycount = 0;
}
@ -464,22 +492,32 @@ void BufferPainter::Finish()
{
FinishPathJob();
FinishFillJob();
if(co_clear)
CoFor(ip->GetHeight(), [&](int y) { // clear remaning lines that were not painted yet
if(co_clear[y]) {
UPP::Fill((*ip)[y], co_clear_color, ip->GetWidth());
co_clear[y] = false;
}
});
}
void BufferPainter::FillOp(const RGBA& color)
{
RenderPath(FILL, Null, color);
One<SpanSource> none;
RenderPath(FILL, none, color);
}
void BufferPainter::StrokeOp(double width, const RGBA& color)
{
RenderPath(width, Null, color);
One<SpanSource> none;
RenderPath(width, none, color);
}
void BufferPainter::ClipOp()
{
FinishPathJob();
Buffer<ClippingLine> newclip = RenderPath(CLIP, Null, RGBAZero());
One<SpanSource> none;
Buffer<ClippingLine> newclip = RenderPath(CLIP, none, RGBAZero());
if(attr.hasclip)
clip.Top() = pick(newclip);
else {

View file

@ -6,7 +6,7 @@ struct GlyphPainter : NilPainter, LinearPathConsumer {
Vector<float> glyph;
double tolerance;
Pointf pos, move;
virtual void LineOp(const Pointf& p, bool);
virtual void MoveOp(const Pointf& p, bool);
virtual void QuadraticOp(const Pointf& p1, const Pointf& p, bool);
@ -59,58 +59,19 @@ void GlyphPainter::CloseOp()
Line(move);
}
struct GlyphKey {
Font fnt;
int chr;
double tolerance;
bool operator==(const GlyphKey& b) const {
return fnt == b.fnt && chr == b.chr && tolerance == b.tolerance;
}
hash_t GetHashValue() const {
return CombineHash(fnt, chr, tolerance);
}
};
struct sMakeGlyph : LRUCache<Value, GlyphKey>::Maker {
GlyphKey gk;
GlyphKey Key() const { return gk; }
int Make(Value& v) const {
GlyphPainter gp;
gp.move = gp.pos = Null;
gp.tolerance = gk.tolerance;
PaintCharacter(gp, Pointf(0, 0), gk.chr, gk.fnt);
int sz = gp.glyph.GetCount() * 4;
v = RawPickToValue(pick(gp.glyph));
return sz;
}
};
void ApproximateChar(LinearPathConsumer& t, Pointf at, int ch, Font fnt, double tolerance)
{
PAINTER_TIMING("ApproximateChar");
Value v;
INTERLOCKED {
PAINTER_TIMING("ApproximateChar::Fetch");
static LRUCache<Value, GlyphKey> cache;
cache.Shrink(500000);
sMakeGlyph h;
h.gk.fnt = fnt;
h.gk.chr = ch;
h.gk.tolerance = tolerance;
v = cache.Get(h);
#ifdef _DEBUG0
DLOG("==== ApproximateChar " << ch << " " << (char)ch << " " << fnt << ", tolerance: " << tolerance);
DDUMP(ValueTo< Vector<float> >(v));
GlyphPainter chp;
chp.move = chp.pos = Null;
chp.tolerance = tolerance;
PaintCharacter(chp, Pointf(0, 0), ch, fnt);
DDUMP(chp.glyph);
ASSERT(ValueTo< Vector<float> >(v) == chp.glyph);
#endif
}
Value v = MakeValueTL([&] { StringBuffer h; RawCat(h, fnt); RawCat(h, tolerance); RawCat(h, ch); return (String)h; },
[&](Value& v) {
GlyphPainter gp;
gp.move = gp.pos = Null;
gp.tolerance = tolerance;
PaintCharacter(gp, Pointf(0, 0), ch, fnt);
int sz = gp.glyph.GetCount() * 4;
v = RawPickToValue(pick(gp.glyph));
return sz;
});
const Vector<float>& g = ValueTo< Vector<float> >(v);
int i = 0;
while(i < g.GetCount()) {

View file

@ -31,7 +31,7 @@ struct MyApp : TopWindow {
{
Refresh();
}
MyApp() {
Sizeable().Zoomable();
SetRect(0, 0, 200, 200);
@ -39,7 +39,7 @@ struct MyApp : TopWindow {
method.Add(FILTER_NEAREST, "Nearest");
method.Add(FILTER_BILINEAR, "Bilinear");
method.Add(FILTER_BSPLINE, "Bspline");
method.Add(FILTER_COSTELLO, "Costello");
method.Add(FILTER_COSTELLO, "Costella");
method.Add(FILTER_BICUBIC_MITCHELL, "Bicubic Mitchell");
method.Add(FILTER_BICUBIC_CATMULLROM, "Bicubic Catmull Rom");
method.Add(FILTER_LANCZOS2, "Lanczos 2");
@ -49,7 +49,10 @@ struct MyApp : TopWindow {
method.Add(-1, "Painter");
method <<= THISBACK(Sync);
Add(method.TopPos(0, STDSIZE).RightPos(0, 200));
method <<= FILTER_NEAREST;
method <<= FILTER_LANCZOS3;
Size sz = TestImg::test().GetSize();
SetRect(0, 0, sz.cx * 4, sz.cy * 4);
}
};

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
[ $$0,0#00000000000000000000000000000000:Default]
[*C@3+75 $$1,1#36268203433472503231438721581057:code]
[*/+117 $$2,0#07143242482611002448121871408047:title]
[@(128.0.255)2 $$3,0#65874547464505293575048467215454:QTF Chr]
[ $$0,0#00000000000000000000000000000000:Default]
[{_}%EN-US
[s0; [*4 QTF]&]
[s0;= [*8 QTF]&]
@ -17,16 +17,16 @@ and characters&]
[s0; &]
[s0; [*@4 . , ; ! ? % ( ) / < > #]&]
[s0; &]
[s0; and bytes [* greater] than 127 are guaranteed to be never used as
command characters (not even in future versions of QTF). Other
[s0; and bytes [* greater] than 127 are guaranteed to be never used
as command characters (not even in future versions of QTF). Other
characters should be prefixed with escape character `` (reverse
apostrophe). Group of characters can be escaped using byte 1.
Example:&]
[s0; &]
[s1; `\"`\1a`[x`]`\1`[`* bold`]`\"&]
[s1; `\`"`\1a`[x`]`\1`[`* bold`]`\`"&]
[s0; Normal [* Bold] [/ Italic] [*/ Bold Italic] [_ Underline] [- Stroked]&]
[s0; Normal [* Bold] [/ Italic] [*/ Bold Italic] [_ Underline] [- Stroked]&]
[s0; Normal [* Bold] [/ Italic] [*/ Bold Italic] [_ Underline] [- Stroked]&]
[Rs0; Normal [* Bold] [/ Italic] [*/ Bold Italic] [_ Underline] [- Stroked]&]
[Cs0; Normal [* Bold] [/ Italic] [*/ Bold Italic] [_ Underline] [- Stroked]&]
[s0; Byte 0 represents the end of input sequence.&]
[s0; &]
[s0; Dimension units of QTF are dots `- one dot is defined as 1/600
@ -66,3 +66,4 @@ with meaning&]
:: [s0;%- [1 LtCyan]]
:: [s0;%- [1 Yellow]]}}&]
[s0; &]
[s0; ]]

View file

@ -0,0 +1,10 @@
uses
CtrlLib;
file
test.iml,
main.cpp;
mainconfig
"" = "GUI";

View file

@ -0,0 +1,69 @@
#include <CtrlLib/CtrlLib.h>
#define IMAGECLASS TestImg
#define IMAGEFILE <CachedRescale/test.iml>
#include <Draw/iml_header.h>
#define IMAGECLASS TestImg
#define IMAGEFILE <CachedRescale/test.iml>
#include <Draw/iml_source.h>
using namespace Upp;
struct MyApp : TopWindow {
typedef MyApp CLASSNAME;
DropList method;
Option pad;
int rotate = 20;
void LeftDown(Point p, dword keyflags) override
{
rotate = p.y;
Refresh();
}
void Paint(Draw& w) override
{
Size sz = GetSize();
DrawPainter p(w, sz);
p.Clear(White());
p.Co();
p.Rectangle(0, 0, sz.cx, sz.cy)
.ImageFilter(~method)
.Fill(TestImg::test(), 20, 20, sz.cx - 40,
rotate, pad ? FILL_HREFLECT|FILL_VPAD : 0);
}
void Sync()
{
Refresh();
}
MyApp() {
Sizeable().Zoomable();
SetRect(0, 0, 40 + 4 * 180, 40 + 4 * 180);
method.Add(FILTER_NEAREST, "Nearest");
method.Add(FILTER_BILINEAR, "Bilinear");
method.Add(FILTER_BSPLINE, "Bspline");
method.Add(FILTER_COSTELLA, "Costello");
method.Add(FILTER_BICUBIC_MITCHELL, "Bicubic Mitchell");
method.Add(FILTER_BICUBIC_CATMULLROM, "Bicubic Catmull Rom");
method.Add(FILTER_LANCZOS2, "Lanczos 2");
method.Add(FILTER_LANCZOS3, "Lanczos 3");
method.Add(FILTER_LANCZOS4, "Lanczos 4");
method.Add(FILTER_LANCZOS5, "Lanczos 5");
method <<= THISBACK(Sync);
Add(method.TopPos(0, STDSIZE).RightPos(0, 200));
method <<= FILTER_NEAREST;
pad.SetLabel("Reflect/Pad");
Add(pad.TopPos(0, STDSIZE).LeftPos(0, 200));
pad << [=] { Refresh(); };
}
};
GUI_APP_MAIN
{
MyApp().Run();
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
uses
CtrlLib;
file
test.iml,
main.cpp;
mainconfig
"" = "GUI";

View file

@ -0,0 +1,91 @@
#include <CtrlLib/CtrlLib.h>
#define IMAGECLASS TestImg
#define IMAGEFILE <PainterImage/test.iml>
#include <Draw/iml_header.h>
#define IMAGECLASS TestImg
#define IMAGEFILE <PainterImage/test.iml>
#include <Draw/iml_source.h>
using namespace Upp;
struct MyApp : TopWindow {
typedef MyApp CLASSNAME;
DropList method;
DropList image;
Option border;
Option over;
int rotate = 20;
void LeftDown(Point p, dword keyflags) override
{
rotate = p.y;
Refresh();
}
void Paint(Draw& w) override
{
Size sz = GetSize();
Image img = ~image == 0 ? TestImg::test2() : TestImg::test();
Size isz = img.GetSize();
{
DrawPainter p(w, sz);
p.Clear(White());
// p.Co();
int q = 8 * !!border;
p.Rectangle(20 - q, 20 - q, isz.cx + 2 * q, isz.cy + 2 * q)
.ImageFilter(~method)
// .Fill(LtRed());
.Fill(img, 20, 20, isz.cx + 20, 20);
DLOG("==============");
p.Rectangle(20 - q, isz.cy + 40 - q, 2 * isz.cx + 2 * q, 2 * isz.cy + 2 * q)
.ImageFilter(~method)
// .Fill(LtRed())
.Fill(img, 20, isz.cy + 40, 2 * isz.cx + 20, isz.cy + 40, FILL_HPAD|FILL_VPAD);
}
w.DrawImage(over ? 100 : 2 * isz.cx + 60, isz.cy + 40, RescaleFilter(img, 2 * isz, ~method));
}
void Sync()
{
Refresh();
}
MyApp() {
Sizeable().Zoomable();
// SetRect(0, 0, 40 + 4 * 180, 40 + 4 * 180);
method.Add(FILTER_NEAREST, "Nearest");
method.Add(FILTER_BILINEAR, "Bilinear");
method.Add(FILTER_BSPLINE, "Bspline");
method.Add(FILTER_COSTELLA, "Costello");
method.Add(FILTER_BICUBIC_MITCHELL, "Bicubic Mitchell");
method.Add(FILTER_BICUBIC_CATMULLROM, "Bicubic Catmull Rom");
method.Add(FILTER_LANCZOS2, "Lanczos 2");
method.Add(FILTER_LANCZOS3, "Lanczos 3");
method.Add(FILTER_LANCZOS4, "Lanczos 4");
method.Add(FILTER_LANCZOS5, "Lanczos 5");
method <<= THISBACK(Sync);
Add(method.BottomPos(0, STDSIZE).RightPos(0, 200));
method <<= FILTER_NEAREST;
border.SetLabel("Extend border");
Add(border.BottomPos(0, STDSIZE).LeftPosZ(0, 200));
Add(image.BottomPos(0, STDSIZE).LeftPosZ(200, 200));
image.Add(0, "Box");
image.Add(1, "Bee");
image <<= 0;
image << [=] { Refresh(); };
border << [=] { Refresh(); };
over.SetLabel("Over border");
Add(over.BottomPos(0, STDSIZE).LeftPosZ(420, 200));
over << [=] { Refresh(); };
}
};
GUI_APP_MAIN
{
MyApp().Run();
}

3075
upptst/PainterImage/test.iml Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
uses
CtrlLib;
file
test.iml,
main.cpp;
mainconfig
"" = "GUI",
"" = "GUI NOSIMD";

View file

@ -0,0 +1,115 @@
#include <CtrlLib/CtrlLib.h>
#define IMAGECLASS TestImg
#define IMAGEFILE <PainterImage/test.iml>
#include <Draw/iml_header.h>
#define IMAGECLASS TestImg
#define IMAGEFILE <PainterImage2/test.iml>
#include <Draw/iml_source.h>
using namespace Upp;
struct MyApp : TopWindow {
typedef MyApp CLASSNAME;
DropList method;
DropList image;
Option border;
Option over;
int rotate = 20;
void LeftDown(Point p, dword keyflags) override
{
rotate = p.y;
Refresh();
}
void Paint(Draw& w) override
{
Image img = TestImg::test();
ImagePainter p(32, 128);
p.Clear(White());
w.DrawRect(GetSize(), WhiteGray());
int y = 1;
for(double sz : { 4, 8, 12, 2, 3, 5, 6, 8, 9 }) {
p.Rectangle(1, y, sz, sz)
.ImageFilter(~method)
.Fill(img, Xform2D::Scale((double)sz / 4) * Xform2D::Translation(1, y), FILL_PAD);
w.DrawImage(32 * 16, 16 * y, Magnify(RescaleFilter(TestImg::test(), sz, sz, ~method), 16, 16));
y += sz + 1;
}
// .Fill(img, 1, 1, 1, 4);
Image m = p;
w.DrawImage(0, 0, Magnify(m, 16, 16));
/*
Size sz = GetSize();
Image img = ~image == 0 ? TestImg::test2() : TestImg::test();
Size isz = img.GetSize();
{
DrawPainter p(w, sz);
p.Clear(White());
// p.Co();
int q = 8 * !!border;
p.Rectangle(20 - q, 20 - q, isz.cx + 2 * q, isz.cy + 2 * q)
.ImageFilter(~method)
// .Fill(LtRed());
.Fill(img, 20, 20, isz.cx + 20, 20);
DLOG("==============");
p.Rectangle(20 - q, isz.cy + 40 - q, 2 * isz.cx + 2 * q, 2 * isz.cy + 2 * q)
.ImageFilter(~method)
// .Fill(LtRed())
.Fill(img, 20, isz.cy + 40, 2 * isz.cx + 20, isz.cy + 40, FILL_HPAD|FILL_VPAD);
}
w.DrawImage(over ? 100 : 2 * isz.cx + 60, isz.cy + 40, RescaleFilter(img, 2 * isz, ~method));
*/
}
void Sync()
{
Refresh();
}
MyApp() {
Sizeable().Zoomable();
// SetRect(0, 0, 40 + 4 * 180, 40 + 4 * 180);
method.Add(FILTER_NEAREST, "Nearest");
method.Add(FILTER_BILINEAR, "Bilinear");
method.Add(FILTER_BSPLINE, "Bspline");
method.Add(FILTER_COSTELLA, "Costello");
method.Add(FILTER_BICUBIC_MITCHELL, "Bicubic Mitchell");
method.Add(FILTER_BICUBIC_CATMULLROM, "Bicubic Catmull Rom");
method.Add(FILTER_LANCZOS2, "Lanczos 2");
method.Add(FILTER_LANCZOS3, "Lanczos 3");
method.Add(FILTER_LANCZOS4, "Lanczos 4");
method.Add(FILTER_LANCZOS5, "Lanczos 5");
method <<= THISBACK(Sync);
Add(method.BottomPos(0, STDSIZE).RightPos(0, 200));
method <<= FILTER_NEAREST;
border.SetLabel("Extend border");
Add(border.BottomPos(0, STDSIZE).LeftPosZ(0, 200));
Add(image.BottomPos(0, STDSIZE).LeftPosZ(200, 200));
image.Add(0, "Box");
image.Add(1, "Bee");
image <<= 0;
image << [=] { Refresh(); };
border << [=] { Refresh(); };
over.SetLabel("Over border");
Add(over.BottomPos(0, STDSIZE).LeftPosZ(420, 200));
over << [=] { Refresh(); };
}
};
GUI_APP_MAIN
{
MyApp().Run();
}

View file

@ -0,0 +1,6 @@
PREMULTIPLIED
IMAGE_ID(test)
IMAGE_BEGIN_DATA
IMAGE_DATA(120,156,59,206,194,192,194,128,4,254,163,227,255,255,33,24,23,31,25,3,0,55,177,24,184,0,0,0,0,0,0,0)
IMAGE_END_DATA(32, 1)