mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
Painter2 (#182)
Painter improvements: Multithreaded rendering improved, new image filter (like Lanczos 3) option, image mapping is now more precise
This commit is contained in:
parent
eb54503041
commit
2d0f19053a
43 changed files with 10281 additions and 3650 deletions
34
benchmarks/CoDo/CoDo.cpp
Normal file
34
benchmarks/CoDo/CoDo.cpp
Normal 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
9
benchmarks/CoDo/CoDo.upp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
uses
|
||||
Core;
|
||||
|
||||
file
|
||||
CoDo.cpp;
|
||||
|
||||
mainconfig
|
||||
"" = "";
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
33
examples/PainterExamples/ImageFilter.cpp
Normal file
33
examples/PainterExamples/ImageFilter.cpp
Normal 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);
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ file
|
|||
Examples.h,
|
||||
main.cpp,
|
||||
Image.cpp,
|
||||
ImageFilter.cpp,
|
||||
Lion.cpp,
|
||||
Pythagoras.cpp,
|
||||
Spiral.cpp,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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](
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -458,4 +458,4 @@ void NoAAFillerFilter::Render(int val)
|
|||
t->Render(val < 128 ? 0 : 256);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ file
|
|||
PaintPainting.cpp,
|
||||
PainterInit.icpp,
|
||||
LinearPath.h,
|
||||
Interpolator.hpp,
|
||||
Xform2D.cpp,
|
||||
Approximate.cpp,
|
||||
Stroker.cpp,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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; ]]
|
||||
10
upptst/PainterFilter/PainterFilter.upp
Normal file
10
upptst/PainterFilter/PainterFilter.upp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
uses
|
||||
CtrlLib;
|
||||
|
||||
file
|
||||
test.iml,
|
||||
main.cpp;
|
||||
|
||||
mainconfig
|
||||
"" = "GUI";
|
||||
|
||||
69
upptst/PainterFilter/main.cpp
Normal file
69
upptst/PainterFilter/main.cpp
Normal 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();
|
||||
}
|
||||
3055
upptst/PainterFilter/test.iml
Normal file
3055
upptst/PainterFilter/test.iml
Normal file
File diff suppressed because it is too large
Load diff
10
upptst/PainterImage/PainterImage.upp
Normal file
10
upptst/PainterImage/PainterImage.upp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
uses
|
||||
CtrlLib;
|
||||
|
||||
file
|
||||
test.iml,
|
||||
main.cpp;
|
||||
|
||||
mainconfig
|
||||
"" = "GUI";
|
||||
|
||||
91
upptst/PainterImage/main.cpp
Normal file
91
upptst/PainterImage/main.cpp
Normal 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
3075
upptst/PainterImage/test.iml
Normal file
File diff suppressed because it is too large
Load diff
11
upptst/PainterImage2/PainterImage2.upp
Normal file
11
upptst/PainterImage2/PainterImage2.upp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
uses
|
||||
CtrlLib;
|
||||
|
||||
file
|
||||
test.iml,
|
||||
main.cpp;
|
||||
|
||||
mainconfig
|
||||
"" = "GUI",
|
||||
"" = "GUI NOSIMD";
|
||||
|
||||
115
upptst/PainterImage2/main.cpp
Normal file
115
upptst/PainterImage2/main.cpp
Normal 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();
|
||||
}
|
||||
6
upptst/PainterImage2/test.iml
Normal file
6
upptst/PainterImage2/test.iml
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue