From 34183b35519a1f1b74a679bbd578f0105a837d70 Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Fri, 3 Apr 2020 19:19:26 +0300 Subject: [PATCH 01/15] implemented another ellipse drawing algorithm --- src/libImaging/Draw.c | 180 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 1 deletion(-) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 58adc1b63..ee3a3938e 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -969,6 +969,184 @@ ellipse(Imaging im, int x0, int y0, int x1, int y1, return 0; } +// Imagine 2D plane and ellipse with center in (0, 0) and semi-major axes a and b. +// Then quarter_* stuff approximates its top right quarter (x, y >= 0) with integer +// points from set {(2x+x0, 2y+y0) | x,y in Z} where x0, y0 are from {0, 1} and +// are such that point (a, b) is in the set. + +typedef struct { + int32_t a, b, cx, cy, ex, ey; + int64_t a2, b2, a2b2; + int8_t finished; +} quarter_state; + +void quarter_init(quarter_state* s, int32_t a, int32_t b) { + if (a < 0 || b < 0) { + s->finished = 1; + } else { + s->a = a; + s->b = b; + s->cx = a; + s->cy = b % 2; + s->ex = a % 2; + s->ey = b; + s->a2 = a * a; + s->b2 = b * b; + s->a2b2 = s->a2 * s->b2; + s->finished = 0; + } +} + +// deviation of the point from ellipse curve, basically a substitution +// of the point into the ellipse equation +int64_t quarter_delta(quarter_state* s, int64_t x, int64_t y) { + return llabs(s->a2 * y * y + s->b2 * x * x - s->a2b2); +} + +int8_t quarter_next(quarter_state* s, int32_t* ret_x, int32_t* ret_y) { + if (s->finished) { + return -1; + } + *ret_x = s->cx; + *ret_y = s->cy; + if (s->cx == s->ex && s->cy == s->ey) { + s->finished = 1; + } else { + // bresenham's algorithm, possible optimization: only consider 2 of 3 + // next points depending on current slope + int32_t nx = s->cx; + int32_t ny = s->cy + 2; + int64_t ndelta = quarter_delta(s, nx, ny); + if (nx > 1) { + int64_t newdelta = quarter_delta(s, s->cx - 2, s->cy + 2); + if (ndelta > newdelta) { + nx = s->cx - 2; + ny = s->cy + 2; + ndelta = newdelta; + } + newdelta = quarter_delta(s, s->cx - 2, s->cy); + if (ndelta > newdelta) { + nx = s->cx - 2; + ny = s->cy; + } + } + s->cx = nx; + s->cy = ny; + } + return 0; +} + +// quarter_* stuff can "draw" a quarter of an ellipse with thickness 1, great. +// Now we use ellipse_* stuff to join all four quarters of two different sized +// ellipses and recieve horizontal segments of a complete ellipse with +// specified thickness. +// +// Still using integer grid with step 2 at this point (like in quarter_*) +// to ease angle clipping in future. + +typedef struct { + quarter_state st_o, st_i; + int32_t py, pl, pr; + int32_t cy, cl, cr; + // 0 - ready to take next quarter piece, 1, 2, 3 - returned 1, 2, 3 hlines + int8_t state; + int8_t finished; +} ellipse_state; + +void ellipse_init(ellipse_state* s, int32_t a, int32_t b, int32_t w) { + s->state = 0; + quarter_init(&s->st_o, a, b); + if (w < 1 || quarter_next(&s->st_o, &s->pr, &s->py) == -1) { + s->finished = 1; + } else { + s->finished = 0; + quarter_init(&s->st_i, a - 2 * (w - 1), b - 2 * (w - 1)); + s->pl = a % 2; + } +} + +int8_t ellipse_next(ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* ret_x1) { + switch (s->state) { + case 0: { + if (s->finished) { + return -1; + } + s->cy = s->py; + s->cl = s->pl; + s->cr = s->pr; + int32_t cx = 0, cy = 0; + int8_t next_ret; + while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= s->cy) { + } + s->pr = cx; + s->py = cy; + if (next_ret == -1) { + s->finished = 1; + } + while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= s->cy) { + s->cl = cx; + } + s->pl = next_ret == -1 ? 0 : cx; + *ret_x0 = -s->cr; + *ret_y = -s->cy; + *ret_x1 = -s->cl; + s->state = 1; + return 0; + } + case 1: { + *ret_x0 = s->cl; + *ret_y = -s->cy; + *ret_x1 = s->cr; + s->state = 2; + return 0; + } + case 2: { + *ret_x0 = -s->cr; + *ret_y = s->cy; + *ret_x1 = -s->cl; + s->state = 3; + return 0; + } + case 3: { + *ret_x0 = s->cl; + *ret_y = s->cy; + *ret_x1 = s->cr; + s->state = 0; + return 0; + } + default: { + assert(0); + return -1; + } + } +} + +static int +ellipseNew(Imaging im, int x0, int y0, int x1, int y1, + const void* ink_, int fill, + int width, int op) +{ + DRAW* draw; + INT32 ink; + DRAWINIT(); + + int a = x1 - x0; + int b = y1 - y0; + if (a < 0 || b < 0) + return 0; + if (fill) { + width = a + b; + } + + ellipse_state st; + ellipse_init(&st, a, b, width); + int32_t X0, Y, X1; + while (ellipse_next(&st, &X0, &Y, &X1) != -1) { + draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); + } + return 0; +} + int ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int width, int op) @@ -988,7 +1166,7 @@ int ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1, const void* ink, int fill, int width, int op) { - return ellipse(im, x0, y0, x1, y1, 0, 360, ink, fill, width, CHORD, op); + return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); } int From 521c43173490ffab7b2011b3e93bc7ba1f20e847 Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Fri, 3 Apr 2020 22:33:47 +0300 Subject: [PATCH 02/15] fixed failing tests --- Tests/images/imagedraw_ellipse_L.png | Bin 359 -> 318 bytes Tests/images/imagedraw_ellipse_RGB.png | Bin 466 -> 408 bytes Tests/images/imagedraw_ellipse_edge.png | Bin 602 -> 622 bytes Tests/images/imagedraw_ellipse_width.png | Bin 452 -> 439 bytes Tests/images/imagedraw_ellipse_width_fill.png | Bin 477 -> 465 bytes .../images/imagedraw_ellipse_width_large.png | Bin 4455 -> 4451 bytes Tests/images/imagedraw_ellipse_zero_width.png | Bin 339 -> 333 bytes Tests/test_imagedraw.py | 4 +- Tests/test_imagedraw2.py | 2 +- src/libImaging/Draw.c | 108 +++++++++--------- 10 files changed, 56 insertions(+), 58 deletions(-) diff --git a/Tests/images/imagedraw_ellipse_L.png b/Tests/images/imagedraw_ellipse_L.png index e47e6e441c16afcc1edb2f7c2a715fd36469c096..d5959cc0820251a9d76a3f3fe0a6ed60fd8dd2ad 100644 GIT binary patch delta 291 zcmV+;0o?xQ0=@!}B!2}-L_t(|obA}%4Z<)G17L$#BP(?OIkG|*;Q=9vM9x0-=TK`u zRY3XVqz}CSj^j9v<2cS~a?Aa^y)Wj>U5^|vEkBSUxk>v50HCXki2NWDjD!pbnNg57 zBmkgu<-XRy)WWYdgcyoK5o!nuLj;2>1d$A?#E57xMR>Z_Cx1lbBBV-rlSGlYN_CS| zk+4d2lhYu{Er~Ye3d?20$bQJ&lYcdS%e%sI9fKq{C3x|!Zj!4KO%UZxGF8G+sJ@9_ zC7$U>6Hy^^BU>&>VY4aS6G5T#FW-2o@EKcpx`-mo@qcq7F=n>~7JB_M}qYeN7002ovPDHLkV1ljyfiM67 delta 332 zcmV-S0ki(T0_OsdB!4bRL_t(|ob8y=4TC@oL*4FiE3p3@tiU4uXc2sHwj+8Vwe}a4 z3=by})dh&6D2k#eihf3qlDln}%NR%lFs(o43de=C9bgCYI3DB(QNUP`c8qvq49fEy z1JiyOdeTW40)P`nCXoyyjDl!}L6J!`Lwf|IISgRpQ8>hy9Dm0tl6XN>TPVaVmV6rq z@rxzgG>IRCd!5h9X%R@FBLE%ayOUf2sm&I>gCwc7g!%uWCxHYq5Hc4Z)%W)ym9v4k z#m?@PiOf;unlM#dMitOvcxMcx_Q0 z1##LUPo^@7rbXz}ud^(mhf`-;;CxnC7;7V8E^s^KiES0000q_jzs<5c@UHIPi;m(x`6TWVTlr_Vto!Cx8eH3Z?Y4t|-67sNH7eJZ&ny!@d1#}R z(c4WYCvALp&S&@QbAJ@$8{bD{OM0)Bx@zu!$!unQ#JM2VOWFdb<5&Ht-kH4AOSD)& zdri*DM{k~Xn+C4BQFU})+y6$*Yj+|<&lea*GP$pLxlya_VbUg+w;Rt%tiEI$<1?)x zf+>l!DtT^6;?!%|-6w?vyd88HH?kaoG4^hZ+iz~ccj4Fna5ae3+l_v^w(Syko1s(I zsC-TGaOw?bpoz;OE+2jxnV-CgWnV<`;ndr+w)kCh(SKgI@^|`-x2gAz@ZO1B8TW7Q z(=)8^-;3US*JsN+cW+Sjhdqgk5AMn`=C^Vl3m2}q|G|0r{*zpYz`8Hc8X5CtYjhY$ N$kWx&Wt~$(69C3+umu1B delta 440 zcmbQie2IC2O8rbv7srr_Id5-Y>^o$@;}SSs|NZ=Oc`j9jjcX1X`)&PhqnyO1&a2eP zfB=7PUWz@CSpB`}U(juB38ScZ@y&gK+pgX*{qXkif1wN7-yhsL`up%NrDcKcDbF@+ z>sX&G6{~Dq+tr;uqhp2ttABa<@_eQ?_d6AqsZXmHGcK8Cx6)wi3@aV)AnV$=U*|rl zq)K{AT}$)L+OD_j*`?OhDkn?#@Um(9*UBARd)`|l>^O@|rdxQ+Rcl7~qf;}iR&QUM zVro(UZcbcf#NE|bS5!$mrmG5sIps!twqbWq4Gk^7X2X4L-4?lJiA;$=2^6A!O1_u+ z?IY8#1xsH`x&3|hrd2cAI9sL$$XzqKxM^$NR&D0FQx$$4PHK84bY%fY_Pq3@Le))~ zH*Uwwy6BpyD_C23l5g8fu^;zNR?OP}o?rHM$E&x8P87^IJ7ao7D@zdAj3saAJASjC-?c5V)piWGQGWL=`_9^zC+?nlC-v9U{r;Vlvvn8l zXIUk!J5#iy&P3KQWLqTfmF1f0R));4?`CyntdGdv>}L2>XW1>4`fP>Z#$%k}X2ll1 zf~DS@mPIa}U9?p&B~o|>R|w0+6yId61;+$aeABcPTD7K{E?wzRxkV{bcs7>^+nLj| z?u9H+S#~r3u;_^sQA@%ab8bvov($(6(nS4K-3+Y>9Ove#7U*T)jAC8)X#R<)J0TB3 zm`(>MOmkgZwlDO8P9uNh6|H(DA*anO6I4G;J1S-KbVX|#=bzJy!Z^z;O}rT*nNA4Z zkdY2!bQC-*n46HwiQn|Dty1W7>o4LXb|oC*-NP@xTf|C|+C{9x~LxH7Zr2X8n3 zQrB1n5jn75XWLqq-7>R)w${7u^s~$YoA_hGQLmnk7NEl-#Gc;z8Roc|#V;jLuUzrr zp2+UsySmOBiR_rNHD{;L!Qit|7s9QI(j@hsYD-VE^;Xc&o>(;Xh)&kEFZ;Ndr|BK; ztexsE{B4b9t>?lmymdzLSu#Iw7f8j6I%dhqs(lC*U#R)5gU z*8cF?Ydxkq`_k14vIYS?!8ZNZ;VUYRv&hbPufEs#jDWtKJm Z#V)P6Zr7Hy+NBIY;OXk;vd$@?2>|5e4hR4M delta 577 zcmaFIa*JhxO1+||i(^Q|oVRlwC$%UDupIpV|EBE*!_$kDSbLc#RhBaCzMOSza>e)1 zZ?*dWH-6t2ujeuX-B%B*V#2#|ZsZxk!Qs2aJeJbN5RiJmC z7Nl0GM>0*{bd4u`-|Lu7VV_HHzdtLn?(ym+mw(%4v&OgDisgQI|LrKd(C!27v8*1` z9L}bFX{x#U_K@kxS+)DuuiIb5^Ze`8pvvs?!o4ZKne*;GNbj8CIXBH}EuXaE;p3U# zUKykv$yxo#MLpH`vncZ%QMJRGC8h=Y5_yB?&-uxGs&eVpPMveE3_#%N>gTe~DWM4f D32FtN diff --git a/Tests/images/imagedraw_ellipse_width.png b/Tests/images/imagedraw_ellipse_width.png index ec0ca6731f69b8728a41a59ae2cd8ebaa0cb71c5..54cfc291ca247de31cc3033575b4993e7f704e11 100644 GIT binary patch delta 413 zcmV;O0b>5d1GfW^B!7NML_t(|obB7ea)Lk*gyD_v|IX%+3lbJ>obFkz|GQe*8M;Ow zvWx)$00000000000C?F?cLQ7Z`A19i+1|uSKr?MUH->c~rmcp~Tf%xXrmblI;jjv0 z+6o;-GOVXboiy{#-g5Z*BoNl&fq#|M^{ssD=39DDo=!5UzzY1XqYQx%{Ai93D-qkHTZ82E_!V(acfUpFF zB_J#TVF?IJP=6bi>uylwx;JLB1cW7Md2aZr70bhF-yOG8*-zkWm>e!|Ek{`E3|Da{ znG<|wxa?I-VTGQjpRQj&;M_V0d|@SrE3{j{eKL0MH;e9PYk6*1HDc+Vf9xq0uv=Nc zK6n9dSgcp(5%2EEcq>fC`-T^-{w6B`000000000002cfLlP)$?u6pK600000NkvXX Hu0mjfwqC&g delta 426 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 QQ&e%#H8X>XtImJ?8*YWK$^ZZW literal 4455 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 diff --git a/Tests/images/imagedraw_ellipse_zero_width.png b/Tests/images/imagedraw_ellipse_zero_width.png index f14a279f2fe2b17911e7fa8ced05c4829d5d7b0f..6661b7d999e37cf6593cc469b9055ff2c048c09c 100644 GIT binary patch delta 306 zcmcc2be3s?O1+4ui(^Q|oVT|P^A0=kusEpdKfiq0%HwgU$2`_ETbibSYidoHyN;Uy z3>voA&i%)v|LWRO*X5^IP36AaUsv3)-#7hP%w+pay`S6H zZhH0b=k-$EsiD%&V$04<4}C0rEsb}2=;n{7h1E~5I`~Gpxc>O5RVyDfUlz}ootM9C zjg;rvkoqNOxp=08{+(zZT0HM8SIP9yvz7lt=O&tIFIdyCiD_+WXnMr#EbmQ`jH#?s zL<7PcV2lI4nFrFqLI>v8GtL(Ma6l8Z;he3*(%H|< z00s@0>&o^wZr@dNaL?V;tCOai+zbi3tETcXYVU)K-xYTk`9)hteqC|HUd%7d*5K#u zNheo{cI!lB)NRVDX-&=MogTXRV|VzSkGVf0CZ}Fa_%^My{`jd?DdZ9_o0v{state = 0; + s->bufcnt = 0; + s->leftmost = a % 2; quarter_init(&s->st_o, a, b); if (w < 1 || quarter_next(&s->st_o, &s->pr, &s->py) == -1) { s->finished = 1; } else { s->finished = 0; quarter_init(&s->st_i, a - 2 * (w - 1), b - 2 * (w - 1)); - s->pl = a % 2; + s->pl = s->leftmost; } } int8_t ellipse_next(ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* ret_x1) { - switch (s->state) { - case 0: { - if (s->finished) { - return -1; - } - s->cy = s->py; - s->cl = s->pl; - s->cr = s->pr; - int32_t cx = 0, cy = 0; - int8_t next_ret; - while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= s->cy) { - } - s->pr = cx; - s->py = cy; - if (next_ret == -1) { - s->finished = 1; - } - while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= s->cy) { - s->cl = cx; - } - s->pl = next_ret == -1 ? 0 : cx; - *ret_x0 = -s->cr; - *ret_y = -s->cy; - *ret_x1 = -s->cl; - s->state = 1; - return 0; - } - case 1: { - *ret_x0 = s->cl; - *ret_y = -s->cy; - *ret_x1 = s->cr; - s->state = 2; - return 0; - } - case 2: { - *ret_x0 = -s->cr; - *ret_y = s->cy; - *ret_x1 = -s->cl; - s->state = 3; - return 0; - } - case 3: { - *ret_x0 = s->cl; - *ret_y = s->cy; - *ret_x1 = s->cr; - s->state = 0; - return 0; - } - default: { - assert(0); + if (s->bufcnt == 0) { + if (s->finished) { return -1; } + int32_t y = s->py; + int32_t l = s->pl; + int32_t r = s->pr; + int32_t cx = 0, cy = 0; + int8_t next_ret; + while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= y) { + } + if (next_ret == -1) { + s->finished = 1; + } else { + s->pr = cx; + s->py = cy; + } + while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= y) { + l = cx; + } + s->pl = next_ret == -1 ? s->leftmost : cx; + + if ((l > 0 || l < r) && y > 0) { + s->cl[s->bufcnt] = l == 0 ? 2 : l; + s->cy[s->bufcnt] = y; + s->cr[s->bufcnt] = r; + ++s->bufcnt; + } + if (y > 0) { + s->cl[s->bufcnt] = -r; + s->cy[s->bufcnt] = y; + s->cr[s->bufcnt] = -l; + ++s->bufcnt; + } + if (l > 0 || l < r) { + s->cl[s->bufcnt] = l == 0 ? 2 : l; + s->cy[s->bufcnt] = -y; + s->cr[s->bufcnt] = r; + ++s->bufcnt; + } + s->cl[s->bufcnt] = -r; + s->cy[s->bufcnt] = -y; + s->cr[s->bufcnt] = -l; + ++s->bufcnt; } + --s->bufcnt; + *ret_x0 = s->cl[s->bufcnt]; + *ret_y = s->cy[s->bufcnt]; + *ret_x1 = s->cr[s->bufcnt]; + return 0; } static int From ccd716cf1d0783c4ed1cbe516af0c0d6d9f244be Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Fri, 3 Apr 2020 22:54:37 +0300 Subject: [PATCH 03/15] added stdint.h for MSVC builds --- src/libImaging/Draw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 34d1d4bc5..20fcbe493 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -35,6 +35,7 @@ #include "Imaging.h" #include +#include #define CEIL(v) (int) ceil(v) #define FLOOR(v) ((v) >= 0.0 ? (int) (v) : (int) floor(v)) From e5ac436a51765f107343b8e6dd1bc40165683539 Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Fri, 3 Apr 2020 19:19:26 +0300 Subject: [PATCH 04/15] implemented another ellipse drawing algorithm --- src/libImaging/Draw.c | 180 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 1 deletion(-) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 58adc1b63..ee3a3938e 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -969,6 +969,184 @@ ellipse(Imaging im, int x0, int y0, int x1, int y1, return 0; } +// Imagine 2D plane and ellipse with center in (0, 0) and semi-major axes a and b. +// Then quarter_* stuff approximates its top right quarter (x, y >= 0) with integer +// points from set {(2x+x0, 2y+y0) | x,y in Z} where x0, y0 are from {0, 1} and +// are such that point (a, b) is in the set. + +typedef struct { + int32_t a, b, cx, cy, ex, ey; + int64_t a2, b2, a2b2; + int8_t finished; +} quarter_state; + +void quarter_init(quarter_state* s, int32_t a, int32_t b) { + if (a < 0 || b < 0) { + s->finished = 1; + } else { + s->a = a; + s->b = b; + s->cx = a; + s->cy = b % 2; + s->ex = a % 2; + s->ey = b; + s->a2 = a * a; + s->b2 = b * b; + s->a2b2 = s->a2 * s->b2; + s->finished = 0; + } +} + +// deviation of the point from ellipse curve, basically a substitution +// of the point into the ellipse equation +int64_t quarter_delta(quarter_state* s, int64_t x, int64_t y) { + return llabs(s->a2 * y * y + s->b2 * x * x - s->a2b2); +} + +int8_t quarter_next(quarter_state* s, int32_t* ret_x, int32_t* ret_y) { + if (s->finished) { + return -1; + } + *ret_x = s->cx; + *ret_y = s->cy; + if (s->cx == s->ex && s->cy == s->ey) { + s->finished = 1; + } else { + // bresenham's algorithm, possible optimization: only consider 2 of 3 + // next points depending on current slope + int32_t nx = s->cx; + int32_t ny = s->cy + 2; + int64_t ndelta = quarter_delta(s, nx, ny); + if (nx > 1) { + int64_t newdelta = quarter_delta(s, s->cx - 2, s->cy + 2); + if (ndelta > newdelta) { + nx = s->cx - 2; + ny = s->cy + 2; + ndelta = newdelta; + } + newdelta = quarter_delta(s, s->cx - 2, s->cy); + if (ndelta > newdelta) { + nx = s->cx - 2; + ny = s->cy; + } + } + s->cx = nx; + s->cy = ny; + } + return 0; +} + +// quarter_* stuff can "draw" a quarter of an ellipse with thickness 1, great. +// Now we use ellipse_* stuff to join all four quarters of two different sized +// ellipses and recieve horizontal segments of a complete ellipse with +// specified thickness. +// +// Still using integer grid with step 2 at this point (like in quarter_*) +// to ease angle clipping in future. + +typedef struct { + quarter_state st_o, st_i; + int32_t py, pl, pr; + int32_t cy, cl, cr; + // 0 - ready to take next quarter piece, 1, 2, 3 - returned 1, 2, 3 hlines + int8_t state; + int8_t finished; +} ellipse_state; + +void ellipse_init(ellipse_state* s, int32_t a, int32_t b, int32_t w) { + s->state = 0; + quarter_init(&s->st_o, a, b); + if (w < 1 || quarter_next(&s->st_o, &s->pr, &s->py) == -1) { + s->finished = 1; + } else { + s->finished = 0; + quarter_init(&s->st_i, a - 2 * (w - 1), b - 2 * (w - 1)); + s->pl = a % 2; + } +} + +int8_t ellipse_next(ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* ret_x1) { + switch (s->state) { + case 0: { + if (s->finished) { + return -1; + } + s->cy = s->py; + s->cl = s->pl; + s->cr = s->pr; + int32_t cx = 0, cy = 0; + int8_t next_ret; + while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= s->cy) { + } + s->pr = cx; + s->py = cy; + if (next_ret == -1) { + s->finished = 1; + } + while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= s->cy) { + s->cl = cx; + } + s->pl = next_ret == -1 ? 0 : cx; + *ret_x0 = -s->cr; + *ret_y = -s->cy; + *ret_x1 = -s->cl; + s->state = 1; + return 0; + } + case 1: { + *ret_x0 = s->cl; + *ret_y = -s->cy; + *ret_x1 = s->cr; + s->state = 2; + return 0; + } + case 2: { + *ret_x0 = -s->cr; + *ret_y = s->cy; + *ret_x1 = -s->cl; + s->state = 3; + return 0; + } + case 3: { + *ret_x0 = s->cl; + *ret_y = s->cy; + *ret_x1 = s->cr; + s->state = 0; + return 0; + } + default: { + assert(0); + return -1; + } + } +} + +static int +ellipseNew(Imaging im, int x0, int y0, int x1, int y1, + const void* ink_, int fill, + int width, int op) +{ + DRAW* draw; + INT32 ink; + DRAWINIT(); + + int a = x1 - x0; + int b = y1 - y0; + if (a < 0 || b < 0) + return 0; + if (fill) { + width = a + b; + } + + ellipse_state st; + ellipse_init(&st, a, b, width); + int32_t X0, Y, X1; + while (ellipse_next(&st, &X0, &Y, &X1) != -1) { + draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); + } + return 0; +} + int ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int width, int op) @@ -988,7 +1166,7 @@ int ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1, const void* ink, int fill, int width, int op) { - return ellipse(im, x0, y0, x1, y1, 0, 360, ink, fill, width, CHORD, op); + return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); } int From a491ed6889afd16a9e34f2dbe6c222dcfcde127f Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Fri, 3 Apr 2020 22:33:47 +0300 Subject: [PATCH 05/15] fixed failing tests --- Tests/images/imagedraw_ellipse_L.png | Bin 359 -> 318 bytes Tests/images/imagedraw_ellipse_RGB.png | Bin 466 -> 408 bytes Tests/images/imagedraw_ellipse_edge.png | Bin 602 -> 622 bytes Tests/images/imagedraw_ellipse_width.png | Bin 452 -> 439 bytes Tests/images/imagedraw_ellipse_width_fill.png | Bin 477 -> 465 bytes .../images/imagedraw_ellipse_width_large.png | Bin 4455 -> 4451 bytes Tests/images/imagedraw_ellipse_zero_width.png | Bin 339 -> 333 bytes Tests/test_imagedraw.py | 4 +- Tests/test_imagedraw2.py | 2 +- src/libImaging/Draw.c | 108 +++++++++--------- 10 files changed, 56 insertions(+), 58 deletions(-) diff --git a/Tests/images/imagedraw_ellipse_L.png b/Tests/images/imagedraw_ellipse_L.png index e47e6e441c16afcc1edb2f7c2a715fd36469c096..d5959cc0820251a9d76a3f3fe0a6ed60fd8dd2ad 100644 GIT binary patch delta 291 zcmV+;0o?xQ0=@!}B!2}-L_t(|obA}%4Z<)G17L$#BP(?OIkG|*;Q=9vM9x0-=TK`u zRY3XVqz}CSj^j9v<2cS~a?Aa^y)Wj>U5^|vEkBSUxk>v50HCXki2NWDjD!pbnNg57 zBmkgu<-XRy)WWYdgcyoK5o!nuLj;2>1d$A?#E57xMR>Z_Cx1lbBBV-rlSGlYN_CS| zk+4d2lhYu{Er~Ye3d?20$bQJ&lYcdS%e%sI9fKq{C3x|!Zj!4KO%UZxGF8G+sJ@9_ zC7$U>6Hy^^BU>&>VY4aS6G5T#FW-2o@EKcpx`-mo@qcq7F=n>~7JB_M}qYeN7002ovPDHLkV1ljyfiM67 delta 332 zcmV-S0ki(T0_OsdB!4bRL_t(|ob8y=4TC@oL*4FiE3p3@tiU4uXc2sHwj+8Vwe}a4 z3=by})dh&6D2k#eihf3qlDln}%NR%lFs(o43de=C9bgCYI3DB(QNUP`c8qvq49fEy z1JiyOdeTW40)P`nCXoyyjDl!}L6J!`Lwf|IISgRpQ8>hy9Dm0tl6XN>TPVaVmV6rq z@rxzgG>IRCd!5h9X%R@FBLE%ayOUf2sm&I>gCwc7g!%uWCxHYq5Hc4Z)%W)ym9v4k z#m?@PiOf;unlM#dMitOvcxMcx_Q0 z1##LUPo^@7rbXz}ud^(mhf`-;;CxnC7;7V8E^s^KiES0000q_jzs<5c@UHIPi;m(x`6TWVTlr_Vto!Cx8eH3Z?Y4t|-67sNH7eJZ&ny!@d1#}R z(c4WYCvALp&S&@QbAJ@$8{bD{OM0)Bx@zu!$!unQ#JM2VOWFdb<5&Ht-kH4AOSD)& zdri*DM{k~Xn+C4BQFU})+y6$*Yj+|<&lea*GP$pLxlya_VbUg+w;Rt%tiEI$<1?)x zf+>l!DtT^6;?!%|-6w?vyd88HH?kaoG4^hZ+iz~ccj4Fna5ae3+l_v^w(Syko1s(I zsC-TGaOw?bpoz;OE+2jxnV-CgWnV<`;ndr+w)kCh(SKgI@^|`-x2gAz@ZO1B8TW7Q z(=)8^-;3US*JsN+cW+Sjhdqgk5AMn`=C^Vl3m2}q|G|0r{*zpYz`8Hc8X5CtYjhY$ N$kWx&Wt~$(69C3+umu1B delta 440 zcmbQie2IC2O8rbv7srr_Id5-Y>^o$@;}SSs|NZ=Oc`j9jjcX1X`)&PhqnyO1&a2eP zfB=7PUWz@CSpB`}U(juB38ScZ@y&gK+pgX*{qXkif1wN7-yhsL`up%NrDcKcDbF@+ z>sX&G6{~Dq+tr;uqhp2ttABa<@_eQ?_d6AqsZXmHGcK8Cx6)wi3@aV)AnV$=U*|rl zq)K{AT}$)L+OD_j*`?OhDkn?#@Um(9*UBARd)`|l>^O@|rdxQ+Rcl7~qf;}iR&QUM zVro(UZcbcf#NE|bS5!$mrmG5sIps!twqbWq4Gk^7X2X4L-4?lJiA;$=2^6A!O1_u+ z?IY8#1xsH`x&3|hrd2cAI9sL$$XzqKxM^$NR&D0FQx$$4PHK84bY%fY_Pq3@Le))~ zH*Uwwy6BpyD_C23l5g8fu^;zNR?OP}o?rHM$E&x8P87^IJ7ao7D@zdAj3saAJASjC-?c5V)piWGQGWL=`_9^zC+?nlC-v9U{r;Vlvvn8l zXIUk!J5#iy&P3KQWLqTfmF1f0R));4?`CyntdGdv>}L2>XW1>4`fP>Z#$%k}X2ll1 zf~DS@mPIa}U9?p&B~o|>R|w0+6yId61;+$aeABcPTD7K{E?wzRxkV{bcs7>^+nLj| z?u9H+S#~r3u;_^sQA@%ab8bvov($(6(nS4K-3+Y>9Ove#7U*T)jAC8)X#R<)J0TB3 zm`(>MOmkgZwlDO8P9uNh6|H(DA*anO6I4G;J1S-KbVX|#=bzJy!Z^z;O}rT*nNA4Z zkdY2!bQC-*n46HwiQn|Dty1W7>o4LXb|oC*-NP@xTf|C|+C{9x~LxH7Zr2X8n3 zQrB1n5jn75XWLqq-7>R)w${7u^s~$YoA_hGQLmnk7NEl-#Gc;z8Roc|#V;jLuUzrr zp2+UsySmOBiR_rNHD{;L!Qit|7s9QI(j@hsYD-VE^;Xc&o>(;Xh)&kEFZ;Ndr|BK; ztexsE{B4b9t>?lmymdzLSu#Iw7f8j6I%dhqs(lC*U#R)5gU z*8cF?Ydxkq`_k14vIYS?!8ZNZ;VUYRv&hbPufEs#jDWtKJm Z#V)P6Zr7Hy+NBIY;OXk;vd$@?2>|5e4hR4M delta 577 zcmaFIa*JhxO1+||i(^Q|oVRlwC$%UDupIpV|EBE*!_$kDSbLc#RhBaCzMOSza>e)1 zZ?*dWH-6t2ujeuX-B%B*V#2#|ZsZxk!Qs2aJeJbN5RiJmC z7Nl0GM>0*{bd4u`-|Lu7VV_HHzdtLn?(ym+mw(%4v&OgDisgQI|LrKd(C!27v8*1` z9L}bFX{x#U_K@kxS+)DuuiIb5^Ze`8pvvs?!o4ZKne*;GNbj8CIXBH}EuXaE;p3U# zUKykv$yxo#MLpH`vncZ%QMJRGC8h=Y5_yB?&-uxGs&eVpPMveE3_#%N>gTe~DWM4f D32FtN diff --git a/Tests/images/imagedraw_ellipse_width.png b/Tests/images/imagedraw_ellipse_width.png index ec0ca6731f69b8728a41a59ae2cd8ebaa0cb71c5..54cfc291ca247de31cc3033575b4993e7f704e11 100644 GIT binary patch delta 413 zcmV;O0b>5d1GfW^B!7NML_t(|obB7ea)Lk*gyD_v|IX%+3lbJ>obFkz|GQe*8M;Ow zvWx)$00000000000C?F?cLQ7Z`A19i+1|uSKr?MUH->c~rmcp~Tf%xXrmblI;jjv0 z+6o;-GOVXboiy{#-g5Z*BoNl&fq#|M^{ssD=39DDo=!5UzzY1XqYQx%{Ai93D-qkHTZ82E_!V(acfUpFF zB_J#TVF?IJP=6bi>uylwx;JLB1cW7Md2aZr70bhF-yOG8*-zkWm>e!|Ek{`E3|Da{ znG<|wxa?I-VTGQjpRQj&;M_V0d|@SrE3{j{eKL0MH;e9PYk6*1HDc+Vf9xq0uv=Nc zK6n9dSgcp(5%2EEcq>fC`-T^-{w6B`000000000002cfLlP)$?u6pK600000NkvXX Hu0mjfwqC&g delta 426 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 QQ&e%#H8X>XtImJ?8*YWK$^ZZW literal 4455 zcmbtYc{r4P_a_xGOemSLuY;^J3fVJc>?vCdm9-~fkjXOHvhOC@NrV==YLF$%%xES{ zv|x}%cDMA&x^4l{1HosKn3} zeKxhMYNZP?jaX6Us{i>Y)9WS;*;2nwqa*LQW|kbpb$88(n_{@EjgsU)rk7UPA*E7Q zvcr1o>@^*6I*xxGTQEF*!Z8!!g;?-ckZd9eA%r);3NkYNf*fNTQq9@yy@`dOt+UWq z3CIMn@Per#&0Bi&*Zf z4#5ZvVm!Lq8i^Acuy!3@4>Y4f#qzrDjWqW_s$-UktF@cx6;mfjD zP~B+M9_ELq?VfdtK9p?Z`bH#C_qu4ZF(ls8@V(@oCL7e8Cv0uU)vNexDn-1c#ouFW z(P)4kaAxu(^(tvx`SWZ?iDwn8-|ZD%O@2C9es@`zo+B&Le2Jgdk!sZwtNP^Q(e!;t zh-;&vpiax6*VqhfTq8?&UN43R2D!gpM1?i11eA27&d2K%<#iL4->BOF?k*vClh}9- zfTLfIT6N0(`$5gy+T8)~7KLDRhO#c@p38G#df_a1Zn$m`Lf4fAdyO=LA@q!X(I;is zO26dvaUyx2)jJ*0ZgKM*yL@D|tqboaeZD@Iuf&r&h*L^5613F-VEuxQqC8D&ZHGnk~7+G0n2nYO0yuDRbbr5=~Imn}|MopNW>#;9zcx)||QChJV z{)QT<4os6cXCHJ%B7i|zj>eHsox*fAIQy}MuV`WVE^dD{vOJA}Cb6F#ZGTT_58rdI z_T8$V0Z>z?uQ~LbR!nSPb4ai$INrXxi~6t;mU~>C^69i~Tg^3kOa{$vvgo9z!)-YC z;j+cB_Hml_C_Do>bLf$>K-^NTncAi{pImB<6|_&e5w5}LiwxOi3wC_!HSL;P;~o2G zH4H_J@yTiR77#|J?0t4Ki-=xu0Aas=i&rqSGfduen?Z{%HQVB%CZ&qn4uQSbb1|X` zUsr~toO%u{h2_Ma`|(taSf)!9+8k79RBWigQsdi&|(>ZT}B zYO6d4gyOw#;P|lmJ!9}hFZPK&T8;P?@_00CR0OlXQEa`%?1iH;9T#d4ri+!-ZMExlP|Y*Hxo2tV)VHMV5ecGHht?tdR#BOZge5g=CBw7gn|sn3vT5xmQ9#XV32pncW36&^ z68}kp+={q$zVa~d8l}_Vo#hm+Ey~q%EHv}>WBXd`DfGMmb}nCUGaDR4zq^j52qD5GP$|vhANiIy#DDUn@lD9&2j`P;%&-K>NyVlon7__V|Jv z;_Ynf{d~W|!r9h)dn-IUUk$@ybc;Zb^bZx)iAWk?v|eHZPgZWvbx#pU)Ajd}aYvmE zb0O#xYXSxJJ@&VR$-I<)AWYx8Ml*nt^xkZmZCtd_0DdCrdDeu+S zWP*+`U0E|(yFw$no+iG5&j&W{UK}cN14+h$^-aQp{GKEeoCn=V!0Z;(1c3zSN_W* zF{!#+Msi1o}sn0^kq*KF#__WW;P3S`C?G>ftvJr)=G$0>j z_jUj~VE`2Cj1EN?tu#K%6!-6*Xd|@$&4IaB$eS`h+mY?gn*glAWL_L;Qt-vuQ5wKjP(wCKsTglAT znm4G0dI6Z;C`f_kwRbirT7GW`2iW$#7NN^C1^N{<-n9X=j{O+md^W_)%a|ypql}Dk zg!X<8Oey5@GkFz}&U_o@0%oQ@reoI?()#VdFU46({IQ)dov(8iSPpl7XhPesR`p92 zmKRtyvZhAf>%r(AOq;ss&a?<>0EpcJ3tWoNVl4gwi#5aT^LeokrVLeemau>8@yn6_ z+2Xe}s4lQ`84{%%UsH`^$nC_4t~`kK{y*lkjQxKO(=B(}7@Sda!C#&<(01y|BB4fF zJ&vROf7+Pt=$EhU8B*5rp<0 zf0HdgPRFGo{7dpb5d9&9o^j**mbmoLQ(IvsR`O}#32rwGMHMb}{Npedh74yElI*?z4o~3S9woCGyHpOU><$eQZ`*ziQkJBPanSeb$7RNe;`Ywcuv&VZpJp#_L(R`W7YmNl@ zmLYz)5r2jD`=Fa;Opf#y4Uf6CqWU>_MCb4DX9NA%+yR#=k*m{}*6H9}8!p~1n+6V~2|wicbeDqOQKvyi=`Hi5-@Tgzdy#1HT8+6iFw4S?3C6 z+CX?1$yp%B^*-7*;4#%g1Y&x3iriqDL@0J!l%=Ci^XvE-+sdw`2IuB*-C0i6{2Uml zhO%t2@Os27(O3Nn+ZL7PQDjv9H`pXzqGf8jM)NLWcDBNUj6+j(Z2d!RPrkAUQ45p4 zlRMi!WOd&-clKH)E?lsHf(WfvTf@({WdW6gdgk2@itpzP+alQBoEGyQakpj%}q%4MwY6*I_+8qm+QxPMb)rcM1{}n@CJr zl+>+w)pOog4*|wvGaNKQ}GZ(oFV^!!cyDx#t(j zH>XC%L}zAr1vOJ}4Bti`!{PL#7cFG%?w%uLL@|y5XXd!p`WUnus?wN8NCQYg9cOVM z0#^S=C9B<=XwRy1zS=$D`*F4-t9OJ+t7fnnUHm246td0Kwzid?r|{kF3$Z>{dJpsO zDCh-cn6YvX2U?>$@HUL!QXt(@pePBZEh2D-2N#nBlU(L6@CFop_*wL_)=oA3i?6q4 zU_juYY263QJtF}R?SnGC*^g#rlO3ayyagDUc|`a=A4vqs!LQa$F8vGWvXD~1;bo2W z2#VzB%sEHTPc6NN&eHP39a>+7zOm0t8NVH?H@$ERtr~DtzT{WyEdpQv-HUrWb{sgi zMsk>cIdr6ad=kYrXcfV3@tWDT)#ZJ!7|mw}fxq*QL(O?ddSh@P?C-80EWGvm9-t7k z{rhGh8nk`zTtC~DUXbGs$19Tg*IwWF9o~H&LSt9tOd=J;u8|;Z&CoDY_hb1o1*7G4 ytXqhRg8}O|5{CBPf$yJU+hARZ{EsJUafj1Nu}e$5{e>25D$3LbS$pzQ%)bCQq-O8{ diff --git a/Tests/images/imagedraw_ellipse_zero_width.png b/Tests/images/imagedraw_ellipse_zero_width.png index f14a279f2fe2b17911e7fa8ced05c4829d5d7b0f..6661b7d999e37cf6593cc469b9055ff2c048c09c 100644 GIT binary patch delta 306 zcmcc2be3s?O1+4ui(^Q|oVT|P^A0=kusEpdKfiq0%HwgU$2`_ETbibSYidoHyN;Uy z3>voA&i%)v|LWRO*X5^IP36AaUsv3)-#7hP%w+pay`S6H zZhH0b=k-$EsiD%&V$04<4}C0rEsb}2=;n{7h1E~5I`~Gpxc>O5RVyDfUlz}ootM9C zjg;rvkoqNOxp=08{+(zZT0HM8SIP9yvz7lt=O&tIFIdyCiD_+WXnMr#EbmQ`jH#?s zL<7PcV2lI4nFrFqLI>v8GtL(Ma6l8Z;he3*(%H|< z00s@0>&o^wZr@dNaL?V;tCOai+zbi3tETcXYVU)K-xYTk`9)hteqC|HUd%7d*5K#u zNheo{cI!lB)NRVDX-&=MogTXRV|VzSkGVf0CZ}Fa_%^My{`jd?DdZ9_o0v{state = 0; + s->bufcnt = 0; + s->leftmost = a % 2; quarter_init(&s->st_o, a, b); if (w < 1 || quarter_next(&s->st_o, &s->pr, &s->py) == -1) { s->finished = 1; } else { s->finished = 0; quarter_init(&s->st_i, a - 2 * (w - 1), b - 2 * (w - 1)); - s->pl = a % 2; + s->pl = s->leftmost; } } int8_t ellipse_next(ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* ret_x1) { - switch (s->state) { - case 0: { - if (s->finished) { - return -1; - } - s->cy = s->py; - s->cl = s->pl; - s->cr = s->pr; - int32_t cx = 0, cy = 0; - int8_t next_ret; - while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= s->cy) { - } - s->pr = cx; - s->py = cy; - if (next_ret == -1) { - s->finished = 1; - } - while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= s->cy) { - s->cl = cx; - } - s->pl = next_ret == -1 ? 0 : cx; - *ret_x0 = -s->cr; - *ret_y = -s->cy; - *ret_x1 = -s->cl; - s->state = 1; - return 0; - } - case 1: { - *ret_x0 = s->cl; - *ret_y = -s->cy; - *ret_x1 = s->cr; - s->state = 2; - return 0; - } - case 2: { - *ret_x0 = -s->cr; - *ret_y = s->cy; - *ret_x1 = -s->cl; - s->state = 3; - return 0; - } - case 3: { - *ret_x0 = s->cl; - *ret_y = s->cy; - *ret_x1 = s->cr; - s->state = 0; - return 0; - } - default: { - assert(0); + if (s->bufcnt == 0) { + if (s->finished) { return -1; } + int32_t y = s->py; + int32_t l = s->pl; + int32_t r = s->pr; + int32_t cx = 0, cy = 0; + int8_t next_ret; + while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= y) { + } + if (next_ret == -1) { + s->finished = 1; + } else { + s->pr = cx; + s->py = cy; + } + while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= y) { + l = cx; + } + s->pl = next_ret == -1 ? s->leftmost : cx; + + if ((l > 0 || l < r) && y > 0) { + s->cl[s->bufcnt] = l == 0 ? 2 : l; + s->cy[s->bufcnt] = y; + s->cr[s->bufcnt] = r; + ++s->bufcnt; + } + if (y > 0) { + s->cl[s->bufcnt] = -r; + s->cy[s->bufcnt] = y; + s->cr[s->bufcnt] = -l; + ++s->bufcnt; + } + if (l > 0 || l < r) { + s->cl[s->bufcnt] = l == 0 ? 2 : l; + s->cy[s->bufcnt] = -y; + s->cr[s->bufcnt] = r; + ++s->bufcnt; + } + s->cl[s->bufcnt] = -r; + s->cy[s->bufcnt] = -y; + s->cr[s->bufcnt] = -l; + ++s->bufcnt; } + --s->bufcnt; + *ret_x0 = s->cl[s->bufcnt]; + *ret_y = s->cy[s->bufcnt]; + *ret_x1 = s->cr[s->bufcnt]; + return 0; } static int From 8aa2386c77721867a3642c9701b210853c132195 Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Fri, 3 Apr 2020 22:54:37 +0300 Subject: [PATCH 06/15] added stdint.h for MSVC builds --- src/libImaging/Draw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 34d1d4bc5..20fcbe493 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -35,6 +35,7 @@ #include "Imaging.h" #include +#include #define CEIL(v) (int) ceil(v) #define FLOOR(v) ((v) >= 0.0 ? (int) (v) : (int) floor(v)) From 68f87e14ca1c984ebb2d653644b7695cf4305d34 Mon Sep 17 00:00:00 2001 From: Stanislau T <44941959+xtsm@users.noreply.github.com> Date: Sat, 4 Apr 2020 10:17:03 +0300 Subject: [PATCH 07/15] Fix grammar Co-Authored-By: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/libImaging/Draw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 20fcbe493..96884ff78 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -1039,7 +1039,7 @@ int8_t quarter_next(quarter_state* s, int32_t* ret_x, int32_t* ret_y) { // quarter_* stuff can "draw" a quarter of an ellipse with thickness 1, great. // Now we use ellipse_* stuff to join all four quarters of two different sized -// ellipses and recieve horizontal segments of a complete ellipse with +// ellipses and receive horizontal segments of a complete ellipse with // specified thickness. // // Still using integer grid with step 2 at this point (like in quarter_*) From 5923f95f3147dda0763f9f3bb4e820bc38b5bd6c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 10 Apr 2020 12:15:01 +1000 Subject: [PATCH 08/15] Adjusted symmetry test --- Tests/test_imagedraw.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index cc29c097e..7a900283e 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -293,8 +293,11 @@ def test_ellipse_edge(): def test_ellipse_symmetric(): - for bbox in [(24, 25, 76, 75), (25, 25, 75, 75), (25, 24, 75, 76)]: - im = Image.new("RGB", (101, 101)) + for width, bbox in ( + (100, (24, 24, 75, 75)), + (101, (25, 25, 75, 75)), + ): + im = Image.new("RGB", (width, 100)) draw = ImageDraw.Draw(im) draw.ellipse(bbox, fill="green", outline="blue") assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT)) From 305b61ed1c35f5e331e714c611e2766167842e0a Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Fri, 8 May 2020 23:54:17 +0300 Subject: [PATCH 09/15] Added test with various ellipse sizes --- .../imagedraw_ellipse_various_sizes.png | Bin 0 -> 21446 bytes ...imagedraw_ellipse_various_sizes_filled.png | Bin 0 -> 20315 bytes Tests/test_imagedraw.py | 35 ++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 Tests/images/imagedraw_ellipse_various_sizes.png create mode 100644 Tests/images/imagedraw_ellipse_various_sizes_filled.png diff --git a/Tests/images/imagedraw_ellipse_various_sizes.png b/Tests/images/imagedraw_ellipse_various_sizes.png new file mode 100644 index 0000000000000000000000000000000000000000..11a1be6faebea1a31854e4d13975f1a5c12c811e GIT binary patch literal 21446 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

L*eghq9S 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 literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 7a900283e..2f1e947a6 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -355,6 +355,41 @@ def test_ellipse_zero_width(): assert_image_equal(im, expected) +def ellipse_various_sizes_helper(filled): + ellipse_sizes = range(32) + image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1 + im = Image.new("RGB", (image_size, image_size)) + draw = ImageDraw.Draw(im) + + x = 1 + for w in ellipse_sizes: + y = 1 + for h in ellipse_sizes: + border = [x, y, x + w - 1, y + h - 1] + if filled: + draw.ellipse(border, fill="white") + else: + draw.ellipse(border, outline="white") + y += h + 1 + x += w + 1 + + return im + + +def test_ellipse_various_sizes(): + im = ellipse_various_sizes_helper(False) + + with Image.open("Tests/images/imagedraw_ellipse_various_sizes.png") as expected: + assert_image_equal(im, expected) + + +def test_ellipse_various_sizes_filled(): + im = ellipse_various_sizes_helper(True) + + with Image.open("Tests/images/imagedraw_ellipse_various_sizes_filled.png") as expected: + assert_image_equal(im, expected) + + def helper_line(points): # Arrange im = Image.new("RGB", (W, H)) From 70aea26781e2fa3657ee9c00464e5c2084b631cd Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Tue, 16 Jun 2020 20:20:29 +0300 Subject: [PATCH 10/15] Added core clipping tools for ellipses --- src/libImaging/Draw.c | 273 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 96884ff78..6fe76e0b8 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -1120,6 +1120,279 @@ int8_t ellipse_next(ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* return 0; } +typedef enum { + CT_AND, + CT_OR, + CT_CLIP +} clip_type; + +typedef struct clip_node { + clip_type type; + double a, b, c; + struct clip_node* l; + struct clip_node* r; +} clip_node; + +typedef struct event_list { + int32_t x; + int8_t type; + struct event_list* next; +} event_list; + +void clip_tree_transpose(clip_node* root) { + if (root != NULL) { + if (root->type == CT_CLIP) { + double t = root->a; + root->a = root->b; + root->b = t; + } + clip_tree_transpose(root->l); + clip_tree_transpose(root->r); + } +} + +void clip_tree_free(clip_node* root) { + if (root != NULL) { + clip_tree_free(root->l); + clip_tree_free(root->r); + free(root); + } +} + +// Outputs a sequence of open-close events (types -1 and 1) for non-intersecting +// segments sorted by X coordinate. +// Merging nodes (AND, OR) may also accept sequences for intersecting segments, +// i.e. something like correct bracket sequences. +event_list* clip_tree_do_clip(clip_node* root, int32_t x0, int32_t y, int32_t x1) { + if (root == NULL) { + event_list* start = malloc(sizeof(event_list)); + event_list* end = malloc(sizeof(event_list)); + start->x = x0; + start->type = 1; + start->next = end; + end->x = x1; + end->type = -1; + end->next = NULL; + return start; + } + if (root->type == CT_CLIP) { + double eps = 1e-9; + double A = root->a; + double B = root->b; + double C = root->c; + if (fabs(A) < eps) { + if (B * y + C < -eps) { + x0 = 1; + x1 = 0; + } + } else { + // X of intersection + double ix = - (B * y + C) / A; + if (A * x0 + B * y + C < eps) { + x0 = round(fmax(x0, ix)); + } + if (A * x1 + B * y + C < eps) { + x1 = round(fmin(x1, ix)); + } + } + if (x0 <= x1) { + event_list* start = malloc(sizeof(event_list)); + event_list* end = malloc(sizeof(event_list)); + start->x = x0; + start->type = 1; + start->next = end; + end->x = x1; + end->type = -1; + end->next = NULL; + return start; + } else { + return NULL; + } + } + if (root->type == CT_OR || root->type == CT_AND) { + event_list* l1 = clip_tree_do_clip(root->l, x0, y, x1); + event_list* l2 = clip_tree_do_clip(root->r, x0, y, x1); + event_list* ret = NULL; + event_list* tail = NULL; + int32_t k1 = 0; + int32_t k2 = 0; + while (l1 != NULL || l2 != NULL) { + event_list* t; + if (l2 == NULL || (l1 != NULL && (l1->x < l2->x || (l1->x == l2->x && l1->type > l2->type)))) { + t = l1; + k1 += t->type; + assert(k1 >= 0); + l1 = l1->next; + } else { + t = l2; + k2 += t->type; + assert(k2 >= 0); + l2 = l2->next; + } + t->next = NULL; + if ((root->type == CT_OR && ( + (t->type == 1 && (tail == NULL || tail->type == -1)) || + (t->type == -1 && k1 == 0 && k2 == 0) + )) || + (root->type == CT_AND && ( + (t->type == 1 && (tail == NULL || tail->type == -1) && k1 > 0 && k2 > 0) || + (t->type == -1 && tail != NULL && tail->type == 1 && (k1 == 0 || k2 == 0)) + ))) { + if (tail == NULL) { + ret = t; + } else { + tail->next = t; + } + tail = t; + } else { + free(t); + } + } + return ret; + } + return NULL; +} + +typedef struct { + ellipse_state st; + clip_node* root; + event_list* head; + int32_t y; +} clip_ellipse_state; + +void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, int32_t al, int32_t ar) { + if (a < b) { + // transpose the coordinate system + arc_init(s, b, a, w, 90 - ar, 90 - al); + ellipse_init(&s->st, a, b, w); + clip_tree_transpose(s->root); + } else { + ellipse_init(&s->st, a, b, w); + + s->head = NULL; + + // normalize angles: 0 <= al < 360, al <= ar <= al + 360 + if (ar - al >= 360) { + al = 0; + ar = 360; + } else { + al = (al < 0 ? 360 - (-al % 360) : al) % 360; + ar = al + (ar < al ? 360 - ((al - ar) % 360) : ar - al) % 360; + } + + if (ar == al + 360) { + s->root = NULL; + } else { + clip_node* lc = malloc(sizeof(clip_node)); + clip_node* rc = malloc(sizeof(clip_node)); + lc->l = lc->r = rc->l = rc->r = NULL; + lc->type = rc->type = CT_CLIP; + lc->a = -a * sin(al * M_PI / 180.0); + lc->b = b * cos(al * M_PI / 180.0); + lc->c = (a * a - b * b) * sin(al * M_PI / 90.0) / 2.0; + rc->a = a * sin(ar * M_PI / 180.0); + rc->b = -b * cos(ar * M_PI / 180.0); + rc->c = (b * b - a * a) * sin(ar * M_PI / 90.0) / 2.0; + if (al % 180 == 0 || ar % 180 == 0 || al == ar) { + s->root = malloc(sizeof(clip_node)); + s->root->l = lc; + s->root->r = rc; + s->root->type = ar - al < 180 ? CT_AND : CT_OR; + if (al == ar) { + lc = malloc(sizeof(clip_node)); + lc->l = lc->r = NULL; + lc->type = CT_CLIP; + lc->a = al == 0 ? 1 : al == 180 ? -1 : 0; + lc->b = al % 180 ? (al < 180 ? 1 : -1) : 0; + lc->c = 0; + rc = s->root; + s->root = malloc(sizeof(clip_node)); + s->root->l = lc; + s->root->r = rc; + s->root->type = CT_AND; + } + } else if ((al / 180 + ar / 180) % 2 == 1) { + s->root = malloc(sizeof(clip_node)); + s->root->l = malloc(sizeof(clip_node)); + s->root->l->l = malloc(sizeof(clip_node)); + s->root->l->r = lc; + s->root->r = malloc(sizeof(clip_node)); + s->root->r->l = malloc(sizeof(clip_node)); + s->root->r->r = rc; + s->root->type = CT_OR; + s->root->l->type = CT_AND; + s->root->r->type = CT_AND; + s->root->l->l->type = CT_CLIP; + s->root->r->l->type = CT_CLIP; + s->root->l->l->l = s->root->l->l->r = NULL; + s->root->r->l->l = s->root->r->l->r = NULL; + s->root->l->l->a = s->root->l->l->c = 0; + s->root->r->l->a = s->root->r->l->c = 0; + s->root->l->l->b = (al / 180) % 2 == 0 ? 1 : -1; + s->root->r->l->b = (ar / 180) % 2 == 0 ? 1 : -1; + } else { + s->root = malloc(sizeof(clip_node)); + s->root->l = malloc(sizeof(clip_node)); + s->root->r = malloc(sizeof(clip_node)); + s->root->type = s->root->l->type = ar - al < 180 ? CT_AND : CT_OR; + s->root->l->l = lc; + s->root->l->r = rc; + s->root->r->type = CT_CLIP; + s->root->r->l = s->root->r->r = NULL; + s->root->r->a = s->root->r->c = 0; + s->root->r->b = ar < 180 || ar > 540 ? 1 : -1; + } + } + } +} + +void chord_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, int32_t al, int32_t ar) { + ellipse_init(&s->st, a, b, w); + + s->head = NULL; + + // line equation for chord + double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0); + double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0); + s->root = malloc(sizeof(clip_node)); + s->root->l = s->root->r = NULL; + s->root->type = CT_CLIP; + s->root->a = yr - yl; + s->root->b = xl - xr; + s->root->c = -(s->root->a * xl + s->root->b * yl); +} + +void clip_ellipse_free(clip_ellipse_state* s) { + clip_tree_free(s->root); + while (s->head != NULL) { + event_list* t = s->head; + s->head = s->head->next; + free(t); + } +} + +int8_t clip_ellipse_next(clip_ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* ret_x1) { + int32_t x0, y, x1; + while (s->head == NULL && ellipse_next(&s->st, &x0, &y, &x1) >= 0) { + s->head = clip_tree_do_clip(s->root, x0, y, x1); + s->y = y; + } + if (s->head != NULL) { + *ret_y = s->y; + event_list* t = s->head; + s->head = s->head->next; + *ret_x0 = t->x; + free(t); + t = s->head; + assert(t != NULL); + s->head = s->head->next; + *ret_x1 = t->x; + free(t); + return 0; + } + return -1; +} + static int ellipseNew(Imaging im, int x0, int y0, int x1, int y1, const void* ink_, int fill, From 96f69eb287478ee578814f340458025ba04ad1a8 Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Mon, 29 Jun 2020 17:21:33 +0300 Subject: [PATCH 11/15] Replaced drawing algorithm for arcs, chords and pies --- src/_imaging.c | 5 +- src/libImaging/Draw.c | 545 ++++++++++++++++++++++-------------------- 2 files changed, 292 insertions(+), 258 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 40bfbf2fe..d885a3416 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2834,8 +2834,7 @@ _draw_arc(ImagingDrawObject* self, PyObject* args) int ink; int width = 0; float start, end; - int op = 0; - if (!PyArg_ParseTuple(args, "Offi|ii", &data, &start, &end, &ink, &width)) { + if (!PyArg_ParseTuple(args, "Offi|i", &data, &start, &end, &ink, &width)) { return NULL; } @@ -2852,7 +2851,7 @@ _draw_arc(ImagingDrawObject* self, PyObject* args) n = ImagingDrawArc(self->image->image, (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], - start, end, &ink, width, op + start, end, &ink, width, self->blend ); free(xy); diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index ea56d73f0..3eee5983a 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -819,192 +819,6 @@ ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void* ink, /* -------------------------------------------------------------------- */ /* standard shapes */ -#define ARC 0 -#define CHORD 1 -#define PIESLICE 2 - -static void -ellipsePoint(int cx, int cy, int w, int h, - float i, int *x, int *y) -{ - float i_cos, i_sin; - float x_f, y_f; - double modf_int; - i_cos = cos(i*M_PI/180); - i_sin = sin(i*M_PI/180); - x_f = (i_cos * w/2) + cx; - y_f = (i_sin * h/2) + cy; - if (modf(x_f, &modf_int) == 0.5) { - *x = i_cos > 0 ? FLOOR(x_f) : CEIL(x_f); - } else { - *x = FLOOR(x_f + 0.5); - } - if (modf(y_f, &modf_int) == 0.5) { - *y = i_sin > 0 ? FLOOR(y_f) : CEIL(y_f); - } else { - *y = FLOOR(y_f + 0.5); - } -} - -static int -ellipse(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink_, int fill, - int width, int mode, int op) -{ - float i; - int inner; - int n; - int maxEdgeCount; - int w, h; - int x, y; - int cx, cy; - int lx = 0, ly = 0; - int sx = 0, sy = 0; - int lx_inner = 0, ly_inner = 0; - int sx_inner = 0, sy_inner = 0; - DRAW* draw; - INT32 ink; - Edge* e; - - DRAWINIT(); - - while (end < start) { - end += 360; - } - - if (end - start > 360) { - // no need to go in loops - end = start + 361; - } - - w = x1 - x0; - h = y1 - y0; - if (w <= 0 || h <= 0) { - return 0; - } - - cx = (x0 + x1) / 2; - cy = (y0 + y1) / 2; - - if (!fill && width <= 1) { - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; - } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i != start) { - draw->line(im, lx, ly, x, y, ink); - } else { - sx = x, sy = y; - } - lx = x, ly = y; - } - - if (i != start) { - if (mode == PIESLICE) { - if (x != cx || y != cy) { - draw->line(im, x, y, cx, cy, ink); - draw->line(im, cx, cy, sx, sy, ink); - } - } else if (mode == CHORD) { - if (x != sx || y != sy) { - draw->line(im, x, y, sx, sy, ink); - } - } - } - } else { - inner = (mode == ARC || !fill) ? 1 : 0; - - // Build edge list - // malloc check UNDONE, FLOAT? - maxEdgeCount = ceil(end - start); - if (inner) { - maxEdgeCount *= 2; - } - maxEdgeCount += 3; - e = calloc(maxEdgeCount, sizeof(Edge)); - if (!e) { - ImagingError_MemoryError(); - return -1; - } - - // Outer circle - n = 0; - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; - } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i == start) { - sx = x, sy = y; - } else { - add_edge(&e[n++], lx, ly, x, y); - } - lx = x, ly = y; - } - if (n == 0) { - return 0; - } - - if (inner) { - // Inner circle - x0 += width - 1; - y0 += width - 1; - x1 -= width - 1; - y1 -= width - 1; - - w = x1 - x0; - h = y1 - y0; - if (w <= 0 || h <= 0) { - // ARC with no gap in the middle is a PIESLICE - mode = PIESLICE; - inner = 0; - } else { - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; - } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i == start) { - sx_inner = x, sy_inner = y; - } else { - add_edge(&e[n++], lx_inner, ly_inner, x, y); - } - lx_inner = x, ly_inner = y; - } - } - } - - if (end - start < 360) { - // Close polygon - if (mode == PIESLICE) { - if (x != cx || y != cy) { - add_edge(&e[n++], sx, sy, cx, cy); - add_edge(&e[n++], cx, cy, lx, ly); - if (inner) { - ImagingDrawWideLine(im, sx, sy, cx, cy, &ink, width, op); - ImagingDrawWideLine(im, cx, cy, lx, ly, &ink, width, op); - } - } - } else if (mode == CHORD) { - add_edge(&e[n++], sx, sy, lx, ly); - if (inner) { - add_edge(&e[n++], sx_inner, sy_inner, lx_inner, ly_inner); - } - } else if (mode == ARC) { - add_edge(&e[n++], sx, sy, sx_inner, sy_inner); - add_edge(&e[n++], lx, ly, lx_inner, ly_inner); - } - } - - draw->polygon(im, n, e, ink, 0); - - free(e); - } - - return 0; -} - // Imagine 2D plane and ellipse with center in (0, 0) and semi-major axes a and b. // Then quarter_* stuff approximates its top right quarter (x, y >= 0) with integer // points from set {(2x+x0, 2y+y0) | x,y in Z} where x0, y0 are from {0, 1} and @@ -1155,25 +969,35 @@ int8_t ellipse_next(ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* return 0; } +// Clipping tree consists of half-plane clipping nodes and combining nodes. +// We can throw a horizontal segment in such a tree and collect an ordered set +// of resulting disjoint clipped segments organized into a sorted linked list +// of their end points. typedef enum { - CT_AND, - CT_OR, - CT_CLIP + CT_AND, // intersection + CT_OR, // union + CT_CLIP // half-plane clipping } clip_type; typedef struct clip_node { clip_type type; - double a, b, c; - struct clip_node* l; + double a, b, c; // half-plane coeffs, only used in clipping nodes + struct clip_node* l; // child pointers, are only non-NULL in combining nodes struct clip_node* r; } clip_node; +// Linked list for the ends of the clipped horizontal segments. +// Since the segment is always horizontal, we don't need to store Y coordinate. typedef struct event_list { int32_t x; - int8_t type; + int8_t type; // used internally, 1 for the left end (smaller X), -1 for the + // right end; pointless in output since the output segments + // are disjoint, therefore the types would always come in pairs + // and interchange (1 -1 1 -1 ...) struct event_list* next; } event_list; +// Mirrors all the clipping nodes of the tree relative to the y = x line. void clip_tree_transpose(clip_node* root) { if (root != NULL) { if (root->type == CT_CLIP) { @@ -1186,29 +1010,31 @@ void clip_tree_transpose(clip_node* root) { } } -void clip_tree_free(clip_node* root) { - if (root != NULL) { - clip_tree_free(root->l); - clip_tree_free(root->r); - free(root); - } -} - -// Outputs a sequence of open-close events (types -1 and 1) for non-intersecting -// segments sorted by X coordinate. -// Merging nodes (AND, OR) may also accept sequences for intersecting segments, -// i.e. something like correct bracket sequences. -event_list* clip_tree_do_clip(clip_node* root, int32_t x0, int32_t y, int32_t x1) { +// Outputs a sequence of open-close events (types -1 and 1) for +// non-intersecting segments sorted by X coordinate. +// Combining nodes (AND, OR) may also accept sequences for intersecting +// segments, i.e. something like correct bracket sequences. +int clip_tree_do_clip(clip_node* root, int32_t x0, int32_t y, int32_t x1, event_list** ret) { if (root == NULL) { event_list* start = malloc(sizeof(event_list)); + if (!start) { + ImagingError_MemoryError(); + return -1; + } event_list* end = malloc(sizeof(event_list)); + if (!end) { + free(start); + ImagingError_MemoryError(); + return -1; + } start->x = x0; start->type = 1; start->next = end; end->x = x1; end->type = -1; end->next = NULL; - return start; + *ret = start; + return 0; } if (root->type == CT_CLIP) { double eps = 1e-9; @@ -1232,22 +1058,43 @@ event_list* clip_tree_do_clip(clip_node* root, int32_t x0, int32_t y, int32_t x1 } if (x0 <= x1) { event_list* start = malloc(sizeof(event_list)); + if (!start) { + ImagingError_MemoryError(); + return -1; + } event_list* end = malloc(sizeof(event_list)); + if (!end) { + free(start); + ImagingError_MemoryError(); + return -1; + } start->x = x0; start->type = 1; start->next = end; end->x = x1; end->type = -1; end->next = NULL; - return start; + *ret = start; } else { - return NULL; + *ret = NULL; } + return 0; } if (root->type == CT_OR || root->type == CT_AND) { - event_list* l1 = clip_tree_do_clip(root->l, x0, y, x1); - event_list* l2 = clip_tree_do_clip(root->r, x0, y, x1); - event_list* ret = NULL; + event_list* l1; + event_list* l2; + if (clip_tree_do_clip(root->l, x0, y, x1, &l1) < 0) { + return -1; + } + if (clip_tree_do_clip(root->r, x0, y, x1, &l2) < 0) { + while (l1) { + l2 = l1->next; + free(l1); + l1 = l2; + } + return -1; + } + *ret = NULL; event_list* tail = NULL; int32_t k1 = 0; int32_t k2 = 0; @@ -1274,7 +1121,7 @@ event_list* clip_tree_do_clip(clip_node* root, int32_t x0, int32_t y, int32_t x1 (t->type == -1 && tail != NULL && tail->type == 1 && (k1 == 0 || k2 == 0)) ))) { if (tail == NULL) { - ret = t; + *ret = t; } else { tail->next = t; } @@ -1283,43 +1130,59 @@ event_list* clip_tree_do_clip(clip_node* root, int32_t x0, int32_t y, int32_t x1 free(t); } } - return ret; + return 0; } - return NULL; + *ret = NULL; + return 0; } +// One more layer of processing on top of the regular ellipse. +// Uses the clipping tree. +// Used for producing ellipse derivatives such as arc, chord, pie, etc. typedef struct { ellipse_state st; clip_node* root; + clip_node nodes[7]; + int32_t node_count; event_list* head; int32_t y; } clip_ellipse_state; -void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, int32_t al, int32_t ar) { +typedef void (*clip_ellipse_init)(clip_ellipse_state*, int32_t, int32_t, int32_t, float, float); + +// Resulting angles will satisfy 0 <= al < 360, al <= ar <= al + 360 +void normalize_angles(float* al, float* ar) { + if (*ar - *al >= 360) { + *al = 0; + *ar = 360; + } else { + *al = fmod(*al < 0 ? 360 - (fmod(-*al, 360)) : *al, 360); + *ar = *al + fmod(*ar < *al ? 360 - fmod(*al - *ar, 360) : *ar - *al, 360); + } +} + +// An arc with caps orthogonal to the ellipse curve. +void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float _al, float _ar) { if (a < b) { // transpose the coordinate system - arc_init(s, b, a, w, 90 - ar, 90 - al); + arc_init(s, b, a, w, 90 - _ar, 90 - _al); ellipse_init(&s->st, a, b, w); clip_tree_transpose(s->root); } else { + // a >= b, based on "wide" ellipse ellipse_init(&s->st, a, b, w); s->head = NULL; + s->node_count = 0; - // normalize angles: 0 <= al < 360, al <= ar <= al + 360 - if (ar - al >= 360) { - al = 0; - ar = 360; - } else { - al = (al < 0 ? 360 - (-al % 360) : al) % 360; - ar = al + (ar < al ? 360 - ((al - ar) % 360) : ar - al) % 360; - } + int32_t al = round(_al), ar = round(_ar); + // building clipping tree, a lot of different cases if (ar == al + 360) { s->root = NULL; } else { - clip_node* lc = malloc(sizeof(clip_node)); - clip_node* rc = malloc(sizeof(clip_node)); + clip_node* lc = s->nodes + s->node_count++; + clip_node* rc = s->nodes + s->node_count++; lc->l = lc->r = rc->l = rc->r = NULL; lc->type = rc->type = CT_CLIP; lc->a = -a * sin(al * M_PI / 180.0); @@ -1329,30 +1192,30 @@ void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, int32_t al rc->b = -b * cos(ar * M_PI / 180.0); rc->c = (b * b - a * a) * sin(ar * M_PI / 90.0) / 2.0; if (al % 180 == 0 || ar % 180 == 0 || al == ar) { - s->root = malloc(sizeof(clip_node)); + s->root = s->nodes + s->node_count++; s->root->l = lc; s->root->r = rc; s->root->type = ar - al < 180 ? CT_AND : CT_OR; if (al == ar) { - lc = malloc(sizeof(clip_node)); + lc = s->nodes + s->node_count++; lc->l = lc->r = NULL; lc->type = CT_CLIP; lc->a = al == 0 ? 1 : al == 180 ? -1 : 0; lc->b = al % 180 ? (al < 180 ? 1 : -1) : 0; lc->c = 0; rc = s->root; - s->root = malloc(sizeof(clip_node)); + s->root = s->nodes + s->node_count++; s->root->l = lc; s->root->r = rc; s->root->type = CT_AND; } } else if ((al / 180 + ar / 180) % 2 == 1) { - s->root = malloc(sizeof(clip_node)); - s->root->l = malloc(sizeof(clip_node)); - s->root->l->l = malloc(sizeof(clip_node)); + s->root = s->nodes + s->node_count++; + s->root->l = s->nodes + s->node_count++; + s->root->l->l = s->nodes + s->node_count++; s->root->l->r = lc; - s->root->r = malloc(sizeof(clip_node)); - s->root->r->l = malloc(sizeof(clip_node)); + s->root->r = s->nodes + s->node_count++; + s->root->r->l = s->nodes + s->node_count++; s->root->r->r = rc; s->root->type = CT_OR; s->root->l->type = CT_AND; @@ -1366,9 +1229,9 @@ void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, int32_t al s->root->l->l->b = (al / 180) % 2 == 0 ? 1 : -1; s->root->r->l->b = (ar / 180) % 2 == 0 ? 1 : -1; } else { - s->root = malloc(sizeof(clip_node)); - s->root->l = malloc(sizeof(clip_node)); - s->root->r = malloc(sizeof(clip_node)); + s->root = s->nodes + s->node_count++; + s->root->l = s->nodes + s->node_count++; + s->root->r = s->nodes + s->node_count++; s->root->type = s->root->l->type = ar - al < 180 ? CT_AND : CT_OR; s->root->l->l = lc; s->root->l->r = rc; @@ -1381,15 +1244,41 @@ void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, int32_t al } } -void chord_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, int32_t al, int32_t ar) { - ellipse_init(&s->st, a, b, w); +// A chord line. +void chord_line_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) { + ellipse_init(&s->st, a, b, a + b + 1); s->head = NULL; + s->node_count = 0; // line equation for chord double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0); double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0); - s->root = malloc(sizeof(clip_node)); + s->root = s->nodes + s->node_count++; + s->root->l = s->nodes + s->node_count++; + s->root->r = s->nodes + s->node_count++; + s->root->type = CT_AND; + s->root->l->type = s->root->r->type = CT_CLIP; + s->root->l->l = s->root->l->r = s->root->r->l = s->root->r->r = NULL; + s->root->l->a = yr - yl; + s->root->l->b = xl - xr; + s->root->l->c = -(s->root->l->a * xl + s->root->l->b * yl); + s->root->r->a = -s->root->l->a; + s->root->r->b = -s->root->l->b; + s->root->r->c = 2 * w * sqrt(pow(s->root->l->a, 2.0) + pow(s->root->l->b, 2.0)) - s->root->l->c; +} + +// A chord. +void chord_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) { + ellipse_init(&s->st, a, b, w); + + s->head = NULL; + s->node_count = 0; + + // line equation for chord + double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0); + double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0); + s->root = s->nodes + s->node_count++; s->root->l = s->root->r = NULL; s->root->type = CT_CLIP; s->root->a = yr - yl; @@ -1397,8 +1286,35 @@ void chord_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, int32_t s->root->c = -(s->root->a * xl + s->root->b * yl); } +// A pie. Can also be used to draw an arc with ugly sharp caps. +void pie_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) { + ellipse_init(&s->st, a, b, w); + + s->head = NULL; + s->node_count = 0; + + // line equations for pie sides + double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0); + double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0); + + clip_node* lc = s->nodes + s->node_count++; + clip_node* rc = s->nodes + s->node_count++; + lc->l = lc->r = rc->l = rc->r = NULL; + lc->type = rc->type = CT_CLIP; + lc->a = -yl; + lc->b = xl; + lc->c = 0; + rc->a = yr; + rc->b = -xr; + rc->c = 0; + + s->root = s->nodes + s->node_count++; + s->root->l = lc; + s->root->r = rc; + s->root->type = ar - al < 180 ? CT_AND : CT_OR; +} + void clip_ellipse_free(clip_ellipse_state* s) { - clip_tree_free(s->root); while (s->head != NULL) { event_list* t = s->head; s->head = s->head->next; @@ -1409,7 +1325,9 @@ void clip_ellipse_free(clip_ellipse_state* s) { int8_t clip_ellipse_next(clip_ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* ret_x1) { int32_t x0, y, x1; while (s->head == NULL && ellipse_next(&s->st, &x0, &y, &x1) >= 0) { - s->head = clip_tree_do_clip(s->root, x0, y, x1); + if (clip_tree_do_clip(s->root, x0, y, x1, &s->head) < 0) { + return -2; + } s->y = y; } if (s->head != NULL) { @@ -1439,8 +1357,9 @@ ellipseNew(Imaging im, int x0, int y0, int x1, int y1, int a = x1 - x0; int b = y1 - y0; - if (a < 0 || b < 0) - return 0; + if (a < 0 || b < 0) { + return 0; + } if (fill) { width = a + b; } @@ -1449,41 +1368,157 @@ ellipseNew(Imaging im, int x0, int y0, int x1, int y1, ellipse_init(&st, a, b, width); int32_t X0, Y, X1; while (ellipse_next(&st, &X0, &Y, &X1) != -1) { - draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); + draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); } return 0; } -int -ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int width, int op) +static int +clipEllipseNew(Imaging im, int x0, int y0, int x1, int y1, + float start, float end, + const void* ink_, int width, int op, clip_ellipse_init init) { - return ellipse(im, x0, y0, x1, y1, start, end, ink, 0, width, ARC, op); + DRAW* draw; + INT32 ink; + DRAWINIT(); + + int a = x1 - x0; + int b = y1 - y0; + if (a < 0 || b < 0 || start == end) { + return 0; + } + + clip_ellipse_state st; + init(&st, a, b, width, start, end); + int32_t X0, Y, X1; + int next_code; + while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) { + draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); + } + clip_ellipse_free(&st); + return next_code == -1 ? 0 : -1; +} +static int +arcNew(Imaging im, int x0, int y0, int x1, int y1, + float start, float end, + const void* ink_, int width, int op) +{ + return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, arc_init); } -int -ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int fill, - int width, int op) +static int +chordNew(Imaging im, int x0, int y0, int x1, int y1, + float start, float end, + const void* ink_, int width, int op) { - return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, CHORD, op); + return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, chord_init); +} + +static int +chordLineNew(Imaging im, int x0, int y0, int x1, int y1, + float start, float end, + const void* ink_, int width, int op) +{ + return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, chord_line_init); +} + +static int +pieNew(Imaging im, int x0, int y0, int x1, int y1, + float start, float end, + const void* ink_, int op) +{ + return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, x1 + y1 - x0 - y0, op, pie_init); } int ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1, const void* ink, int fill, int width, int op) { + //fprintf(stderr, "E (%d %d) (%d %d) --- %08X f%d w%d o%d\n", x0, y0, x1, y1, *(int*)ink, fill, width, op); return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); } +int +ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, + float start, float end, const void* ink, int width, int op) +{ + //fprintf(stderr, "A (%d %d) (%d %d) %f-%f %08X f- w%d o%d\n", x0, y0, x1, y1, start, end, *(int*)ink, width, op); + normalize_angles(&start, &end); + if (start + 360 == end) { + return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, 0, width, op); + } + if (start == end) { + return 0; + } + return arcNew(im, x0, y0, x1, y1, start, end, ink, width, op); +} + + +int +ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1, + float start, float end, const void* ink, int fill, + int width, int op) +{ + //fprintf(stderr, "C (%d %d) (%d %d) %f-%f %08X f%d w%d o%d\n", x0, y0, x1, y1, start, end, *(int*)ink, fill, width, op); + normalize_angles(&start, &end); + if (start + 360 == end) { + return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op); + } + if (start == end) { + return 0; + } + if (fill) { + return chordNew(im, x0, y0, x1, y1, start, end, ink, x1 - x0 + y1 - y0 + 1, op); + } else { + if (chordLineNew(im, x0, y0, x1, y1, start, end, ink, width, op)) { + return -1; + } + return chordNew(im, x0, y0, x1, y1, start, end, ink, width, op); + } +} + + int ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int fill, int width, int op) { - return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, PIESLICE, op); + //fprintf(stderr, "P (%d %d) (%d %d) %f-%f %08X f%d w%d o%d\n", x0, y0, x1, y1, start, end, *(int*)ink, fill, width, op); + normalize_angles(&start, &end); + if (start + 360 == end) { + return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op); + } + if (start == end) { + return 0; + } + if (fill) { + return pieNew(im, x0, y0, x1, y1, start, end, ink, op); + } else { + float xc = x0 + (x1 - x0) / 2.0, yc = y0 + (y1 - y0) / 2.0; + float al = start * M_PI / 180.0, ar = end * M_PI / 180.0; + int32_t xa = xc + (x1 - x0 - width) * cos(al) / 2, ya = yc + (y1 - y0 - width) * sin(al) / 2; + int32_t xb = xc + (x1 - x0 - width) * cos(ar) / 2, yb = yc + (y1 - y0 - width) * sin(ar) / 2; + int32_t xt, yt; + if (ImagingDrawWideLine(im, xc, yc, xa, ya, ink, width, op) < 0) { + return -1; + } + if (ImagingDrawWideLine(im, xc, yc, xb, yb, ink, width, op) < 0) { + return -1; + } + xt = xc - width / 2; + yt = yc - width / 2; + ellipseNew(im, xt, yt, xt + width, yt + width, ink, 1, 0, op); + xt = xa - width / 2; + yt = ya - width / 2; + ellipseNew(im, xt, yt, xt + width, yt + width, ink, 1, 0, op); + xt = xb - width / 2; + yt = yb - width / 2; + ellipseNew(im, xt, yt, xt + width, yt + width, ink, 1, 0, op); + return arcNew(im, x0, y0, x1, y1, start, end, ink, width, op); + } } + /* -------------------------------------------------------------------- */ /* experimental level 2 ("arrow") graphics stuff. this implements From 9a9d3a050a3e579d579c61549618d297a1e837a8 Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Mon, 29 Jun 2020 22:49:11 +0300 Subject: [PATCH 12/15] Fixed tests --- Tests/images/imagedraw_arc.png | Bin 284 -> 248 bytes Tests/images/imagedraw_arc_end_le_start.png | Bin 218 -> 199 bytes Tests/images/imagedraw_arc_no_loops.png | Bin 384 -> 314 bytes Tests/images/imagedraw_arc_width.png | Bin 439 -> 428 bytes Tests/images/imagedraw_arc_width_fill.png | Bin 438 -> 426 bytes .../imagedraw_arc_width_non_whole_angle.png | Bin 439 -> 430 bytes Tests/images/imagedraw_arc_width_pieslice.png | Bin 402 -> 404 bytes Tests/images/imagedraw_chord_L.png | Bin 257 -> 237 bytes Tests/images/imagedraw_chord_RGB.png | Bin 324 -> 301 bytes Tests/images/imagedraw_chord_width.png | Bin 488 -> 493 bytes Tests/images/imagedraw_chord_width_fill.png | Bin 507 -> 514 bytes Tests/images/imagedraw_chord_zero_width.png | Bin 427 -> 429 bytes Tests/images/imagedraw_pieslice.png | Bin 394 -> 375 bytes Tests/images/imagedraw_pieslice_width.png | Bin 496 -> 493 bytes .../images/imagedraw_pieslice_width_fill.png | Bin 523 -> 519 bytes .../images/imagedraw_pieslice_zero_width.png | Bin 403 -> 405 bytes Tests/test_imagedraw.py | 10 +- src/PIL/ImageDraw.py | 2 +- src/libImaging/Draw.c | 136 ++++++++++++------ 19 files changed, 97 insertions(+), 51 deletions(-) diff --git a/Tests/images/imagedraw_arc.png b/Tests/images/imagedraw_arc.png index b097743890cb1907122e53a98231e8887d791d12..967e214d9bd4ea414f1ca0821e9f8bf9b255c04c 100644 GIT binary patch delta 220 zcmbQk^n-DNO8tIM7srr_Id88x@*P&-VL7nm|9@j!g;1GWt6Umnt%}_e)`!WkGoXN! zaIa3ehrNx#wr;Us_*8vli(w+*0uH&(PCi?pMzU-i@r(V9F8?rSttzCA~s%z@a zJe=BxpPssKSWQw&_Tu3c=@PTU+ty!QFh%HUWnse0zf6a@R#sp6v#&i&=HK+;f$b$@!~Et9J35S*|RNTKdZH-Yz+isLuGaL{{FWANW_;b(VLt=s_KmwkwelF{r5}E*ij&eo- diff --git a/Tests/images/imagedraw_arc_end_le_start.png b/Tests/images/imagedraw_arc_end_le_start.png index aee48e1c6bb7e6c04513e728aa43c61ab03c4657..191cc0b3a6710a68be803020711d080a1010efb0 100644 GIT binary patch delta 171 zcmcb`c${&9N`0TFi(^Q|oVQmTxmpYaTrQ@(tAAdm$|707m7{j3dUwu}pCWyX3}A5J z{ImW1Kh~|f_IytCot;Mow_hup-BR7YYS)`S=}AXbx?fJcsJ%GcZGBIi_zl zPgAXqrAGD`N&(3^^JJ%|vL4~;IQr<|{b^v9r~tPL#AqnlAjiJD$l}S)!|6{!BA%{( JF6*2UngCb5Mg9N) delta 190 zcmX@kc#Cm@O8r7l7srr_Id88X1z*3AeE0q8weGJ0Gjc+cd%mxizV9*ZqW0o& zxAi@7k{gZ0zwV0tc&R=izEL|VclVmKn29epmc@z9-1&IZ8uNyT#=prw)!EdJ-Kvii i?i3Z^hS&uoPu*oaf6MEJ+4Jd7Kti6belF{r5}E)Jx>ZpC diff --git a/Tests/images/imagedraw_arc_no_loops.png b/Tests/images/imagedraw_arc_no_loops.png index e45ad57a5c382d5b01f06a841cbe164acdf76fed..03bbd4b43362900338480cf23dbf65dc9ffbd58b 100644 GIT binary patch delta 287 zcmZo*-o-RQrJm8##WAE}&fDAGeN2WttPWN0>YtxkBz`zez~AeZU&z*{>GqN=C4Z_I z8NlGf*44Wj?2~)`=j~c2Rw^d`QSL&!%f3CgHy)FacgsA=yxnB!?a#`y?>*c&aqa6@ zTlR!~FZPQ&UsB{0=l^X(dEEK%{n>G$rSGSQ=N|2j`iA287`;~6Q%|d>6?UmTt_eGKa(|EUQpx30jn5rS&xttl=G~F9J6xOdB{H_W jxKTe@6yzZZ28IXX-n!S*CDqdQf&@HW{an^LB{Ts5zWRkK delta 357 zcmV-r0h<200)PXMB!5RqL_t(|obB5|Zo?oD1<<(nzcXF9irPhEPXf$<-diW(^J80< z4UY!^00000006fg*#UmE)?R<$IP#+=C-~jbi#l$8Y_yz#pOb%M=AV4`L$k;0HPyt* zCTXo*UYl}%u!=zw+pVt&^Vy`bY13G~ugMcwyRZ4tS1l_m_kXx?zXtto?mxWP@s(cI zGJRDDIwC{=)38EQr#eagRVQNiB%?0yT&r_fo%-f+WCbWIpl4at&cRd7R7GQTOyx8T zP*#Al0+bb?tN>*NC@Vl&0iDaLc3)a8&mYyodFqfxS*hxri5@Fw8d6q;gR4S@TPg%y zziDF)VNGd_)yiSk!N;t=!y;kh z%Q?Lj_wLvg7GS?&K$ZPxmzSPxx5@zk00000089A>4#*Z&#O+kd00000NkvXXu0mjf D^wXWe3%lCeI3)dR1Sudykcg0_W<~$Vi{m zuO6pF5<}l-`kl~T?HhUX(*~*OQ&(!Q4vv%zXBR(I6d@V5mi^U#hg3^}Y0Qa8#223I zo`o-h#P3U&@0kAT+l9@WTsTfEKHQslS5&-xYxL(mW%i%y51)7xw0^7In%b`HpMFa9 zd_QJ9;)-w;y7qd0$luvV_FQ}V>cIjo?M*ulEo0r%zOl+qyW*er!@Unz33bn(qHP&G z%XCRpPLyT&E6aCGm8pDnlO=y$xV`NKv*DWb6ZID5t_9gsuN{x=(OJrT_x10#`yM%0 m$}61`Zq6@3gx-f<_EmFRyo?Vi<}q*sg*{#UT-G@yGywpy=e8OE delta 413 zcmV;O0b>5F1GfW^B!7NML_t(|obB2{Zo@DPL{Ylv{jcmU3}n%y3Yc<+rtsgsu<|20 zwyFpK00000000000Khosd3F!sz3<-3k7)GJ6KhtZ2Y0NYnJ!<>i;XT7&REk+MZ{sa z#yZn+IL1&yKF~#}L76Vv;r>->T&9cl27eh^Dx|SqPOG`4LVp))zf$2`hSN)hDAx0| z+MrY@V(nTgT7C`ZlE>O+T19!R4PF2jv@>g`X%*bFQYvHZy#_-%vo>3U!7@xCRsyjS zh?PLB1Y#u+E1~(UgUGMPhbyKFz*5$F( diff --git a/Tests/images/imagedraw_arc_width_fill.png b/Tests/images/imagedraw_arc_width_fill.png index 9572a60590b364899fec4384487ddfd2da1c985b..6c135ab7679b53d40e59adc04e282ff0364c6fd3 100644 GIT binary patch delta 400 zcmdnSyoz~(N`0ZHi(^Q|oVT|R<}n%av?K<9{I`CO?V6U5494kaEVli7e@ye`gSHu_ zf(!_t5tB9DzE56X|LEb7% zz3tJ|2v?zNkE2)p^S0?P-D#z=;!u&;^xPokv1T>8bH^H*>G0#c*yvK>j5WPfL|hEl zSZBI8hEPL3(nYC3nGV|VzAH5@)4_U!KMXAu(pWF2)!b5{i+{CWsc7_yx>v>vj zP%0F$b}bbxzlL+kV{J37qCD0HFMtc$nYGij3hr4cm9h3-gCU(+o2|iM8Kw{`fmjK| zN+4DOu@Z=t(0tZGWR<22XCOeZe^@uf8rc0BTB z=$u^YYjMv?sDF?3Jo7!1%kVEa)67!$@_ZkXSTDa)-RH5Lt5D4>TQBDc6!#u{Z7=g# zorYh-s$Tvl-jQcYyUwW&JFVP%@Y(lz^5?2qeAI9LwvhE*PIQ;Cy^uD%_|_(KzPk79 z5|s4v?{GA4seDkaaTn+;`%fpig#!Qp000000002M+7&NpfNp?H{0000 zH6{iGXkA?P!|>SSuZ1H1&(^+Q)wtg9?V<;VYXmPpdb-Ih`?y(b?M=6;*;gKI<+`1^ zaQ%&la-p#a&=SK z!_2V$B~F6Z-c`>Me{wb}c=Zgqu*XrYyCUaiiR#D(acC!|MeNUuIJ@rPf=JDlsSXjG z2*UQTRI*0d%E{Mzwbyu;7QeWuBY4P5qats|`;N(nqjEpnl-qqeeIZisYsBZnQOjd4 zr`JE)aj^PSs)Vc1wa@OW>eS~Q-=!lf6q0CpI@MZt!CuML=hJ7NuNSp3pA%~M-*cz# z`6*$tL3iJ5DSEpy{XWyDbCy3`&s~TPzr9G`<@UUV|BmmpJ`=rqQ|+CzC%noQaR0sk p!MRoXYiONf$E$oRMELzU!oT8Yi`V`PDPAX#sHdx+%Q~loCIEa0z{vmr delta 413 zcmZ3-yq$T1N`0NDi(^Q|oVT~4Cmk~2aS2S#`nLb+8TQbh4-(xJwyIpWd&<<-cGJHr_DqY8w4OS3WBN{)wyoc#wp=-ND~DI= z^o7<9G90HH;l#on*A-9ReO-7(dj2oIIEDN>CpJ{=+r)mZ%l5RQxz1lNA6Y|-HF?^G zWp*!52h>N#+)Un>Zh55rYsAl4kA(iPP7jLwwa27B%l>>nq~Hyx__bvUg?JC(ib z7oVM(e&Kq*#n;}gI{$P3MBAmIwW|*Anwl*=zjXQJ{9g}df4Sh@U*x&F=Z!;D!8_g_ zpPD@RJGa*Ef2gr9@aKY4T=|W=V(d+C9%6(CWrEmS{@VZTen~4&uLTKuy85}Sb4q9e E07L7~lK=n! diff --git a/Tests/images/imagedraw_arc_width_pieslice.png b/Tests/images/imagedraw_arc_width_pieslice.png index 950d95dd6b7e25bb3a0e55e0e12099e8e3b8045f..e1aa95e88e51b27ae5334b9b5cac4b003ed1bffa 100644 GIT binary patch delta 377 zcmV-<0fzpP1C#@hB!62;L_t(|obB7q3c^4PMd2vE|10Z41b;vc%}wqM=i9k)>M^xr z#RvcZ000000000002EHM%U`cA4^P8~^TTCo;kQgn7>KoErn}Zy>t?z;x2?2#sc4I} zL8<78^?xbzrt(oT!E_2*kS z)+cqotzx|?Z&&<$ZoQ&38CSyuVkHnOp*WVqZs=L<@i18l^|5@0tNciqtOQ9chv8Bj z1Cy1YiZwf2qH@)RlQq>#x32ay9Jq_3Tv7g+)!9qER>uc?Pp^hmny!ZO+j1@MN8i># z_j=mS^noi@;SIv(3&_}Z=wPyueTmoq4gWE?-8J|Y_wXg0!)oLvZp`R00000NkvXXu0mjf+xN8$ delta 375 zcmbQjJc)UNN`0iKi(^Q|oVPaU~clQ%3n$h z2yoi6&!X?8@1LUCvG?pRtezQ>ZCbVKoBqy>l^r`u^uGBo^M2h|^h+)KZe!J}9h=Qg zd{LOcCfBRg>YCruq$jyM^b*$;CqK36Sru{ae2rB7aRW!8?9&^$T-U^UvmQ4%YB=p} zj@a={wdaKTCZ~2sa8BL$&93=i%F-FfKFI`Kj+oam)nQG`ssBq?=+F9C|7VTJ>)_&j zTa#;-YVSx~;~)BS#{*%lLT0qE-1?jXK{R-qrI89=yG{_rY%AV6z|3*B#uxqvOZoS%<&-BEo40^W$8z TG_hOdJ3wNdu6{1-oD!M<_++&Z diff --git a/Tests/images/imagedraw_chord_L.png b/Tests/images/imagedraw_chord_L.png index a5a0078d042113d2b6eddb217c68bd678d2d451b..6c89da9eaf808c19ce0ef7cb6497d2c4ae73c9f2 100644 GIT binary patch delta 209 zcmV;?051Q50__2iB!9F?L_t(|obA>@3c^4TMA1(0966u|ngiw}E+pb2)!l`V3G(04 zKtAONP6Pk|00000fcshGlih#WCO%*NSNR_L(bIj`P!BFyGC8uz^}2hhHzgS|e~B|C8*+ms{VC~?YDu$_H_ET8RVf{#?j^a zie&3;%GC8eI+I?1Rk4+%*Iz|+U9LQpGP0C(QmJC=OyUMBT3*Xa(t?>NBiSO$G=+7U zBo^oJhtXTCBRKKS;vL09;(?{_`#=#&#uCqpnVqFf@>&xra>>ne7tPW5Av7f8>A}~( fodEy<0N~T?lw=tL{%4O>00000NkvXXu0mjf42Ec9 diff --git a/Tests/images/imagedraw_chord_RGB.png b/Tests/images/imagedraw_chord_RGB.png index af6fc766007870d6dd75c4e0b673415af90459a1..d4592cda109186f66f8d2c250cebff7e64a8d9cd 100644 GIT binary patch delta 275 zcmX@Yw3catay`QrPZ!6KiaBp@9_(UrUt>3mab!Cht$FNGX7{+q_xq(as)wQ! z*04U@P?w$K${v<^ZBk9d{-AXwYrQs4+tOlMAHS|GYHFJ9ORnrqF}7P*T}wOrA8J#C4e#>gC(jG(&EI$W@Nu`KUGcVMpS#ViZyjua`av$1@ujz_>+)Lx QJ9$7tp00i_>zopr02O6@V*mgE delta 298 zcmZ3>bcAVway=urr;B4q#hkY{Pv;$05MT|6`TD>9X5^L1+*OP2NKU9acPr_H=`+D& z3@D(1%YDPz&pc++N?PikmM@#xp7KL+XZR1%?3eNa*H{vLJ=(Y4mXUw!Z?xvhqRFCf zrk*co+Mw2Q*s5>tvbDxG#uAajS%+>V7KnbSSCqeImz}gnJ$3%WTL(jf_o}RsWeaze zU*&e~RI1@#4m*+cZGS66!*qCFSM8l!Iaj}w`%2%MYkRsTOYzRWv~16-$x^n~KR)e= znk04h|IB!Ese`j(4gbElCup1BV7I0we|fS0_O#zW0#me;c4?J~*L*L#`olXuPARRO-_zC4Wt~$(6962rgERmD diff --git a/Tests/images/imagedraw_chord_width.png b/Tests/images/imagedraw_chord_width.png index 33a59b487c4e685f1a12c95323ea861fa17997e3..04d3dadf8d17ac6a8abb0339a63ee33ae8145654 100644 GIT binary patch delta 467 zcmaFC{FZrwO8pj37srr_Id5k>_B9&_u&Dq3Z=cRTaZ#+(frjPF*+cINDQ@UKcH>wd z11dQ2Ah7!9^UwFsyB8LgX=vYdx-6X@&+7Vn*~{Lw`k6b+O!t2L@>O~Fr5!65v!50? zd27X=?E;d&f{xTK(apbjZ~64ptA|!G)yMCw?|-;sW$*vmdB>0J{uX(1VvoghZSBbC zKDjc_Cg{grPn3y{*WBcHJnQ?9sQ9jwx!HL^-G>Z9qtj+G>tT-W8=JlN6*EMIIkDe7f`Stu#_Z`vK6{qS%o+`*p61~}d zlW*;LBb)zCP4C+_E2)`@+GTrX`&)FZ2rfz5TJ);&+``w6aW{P;5;ticE@?lin3BwP ze6RK6j`py!-G|sTB^#^Wn#(LKSdeGz8t``+GfMDFJQSCfZV6^HIduah?&<31vd$@? F2>|-P+w=ec delta 462 zcmaFM{DOIcO8t6I7srr_Id5kl>}xjQVZHnJfBmv{uhpVUCv5um+1sRe*#w?#2Qxbu zP(Z`W8{2Mdd*>MQ@x|UhyBD~B?l|{C@>Zq5&0j(N*#_5bYX55Jo5fAFc|G0l+S5Pf z7gJt%3%-jFU2*qd*v|;NX~MztU2gnZC3`|WxPJQQ?_X4PKj^!qy^Gw>l`4C#z{ZWI zHuU7LWi0`Qudm)r-u{K3@G`vZuXyeNklRdXKmJCZ&e@ zFSs6_n-aRG()`z~Wodtxo(^B8^VB!*W2i*r)w}EOU7wQqqx;77JDZ$dXIDike%acz zCg5k3OPEK^+InT}SGBsT+FiA4)wH#0S6+X>dgh~v$kqMATwidbHowC2vDSpszb1QfvZDmO#dFRt2m9R9(k5nr#64a8T-G@yGywoB CiRN_x diff --git a/Tests/images/imagedraw_chord_width_fill.png b/Tests/images/imagedraw_chord_width_fill.png index 809c3ea1cdffd6aea9bc288f40f8349edb859826..77475d266c60db52b46156cefbda18ccbf141261 100644 GIT binary patch delta 488 zcmey(+{7|LrT(O+i(^Q|oVT+t<{efLVA22kzur$?$#o_7w!)jckIt>kPB~~8Y3<<4 z!hixAn%r~0pMTzOFMsr@OhDLOx69J$@vKX~n(W>9b!yc0h0Dz6?b~;$$|^tW%OjIX zDQ|iA2`f3rOBTN8`@Q+I=)CWrqHZmlb@$fq`b+oPn3w&U|NBn=q5QRmCye(>KKGuw zrhD11>6TBl^IsV>tk&;b^LnGzu6@_Sy*910&HGbxcDiY?LE64ACx1O&KV$OxlbckM zH+)p}ySC=!#CHADE1iXz<6ocXs){#G&Fv1;3Gk}bow`Q6bf4H1(bRp{rihyEn>JOH zbzj&t(VP3k>ZgmY+;?rdXyv{aPgg&ebxsLQ01E8rcK`qY delta 481 zcmV<70UrK>1p5P!B!9w5L_t(|ob8*@a)U4o1eN~(Khu{^r=$kEiSH!4yU)Z(8_8e; z5dZ)H0000000000000269LEvIksU;jNABr=3wk6+bVp8T_eE6JmpiV(UaRTr!+Emt zS*hmy<&)LV|6EN4eL2d#llD^x=Yvc2qdb;7Q&%Ui>Ps8QrGKgJX-oRDPVirhWL|Rq z63S|4I9?<5e5qu;YpQj8x-VlJzBcbv@+FbgNa>v!x{R;+vL>5qjgaLl<7)UC1$^mc zO}s_Q_BC0SJb_eS;>uFodFT0BXU_UblCPPvtWA;OOIca!)vw*xnr--v*Vp2*!s=^% zS>f}wg{(07+J8k>IDBm+E9AcRk`-EC+sO){FQu%kUAiT%hD9D&Juxw=9otZbSeCyN z7rKM5)`@fD(YK+qxL8QnVZN+J<$H=t|9YycvedrT-6?JVrMi4tBP%X7s><^Da+wNg z8#;U$O-0VtaF#Fo9dF5ZO8bLe52;M3-rLHZv2I5aXIkT1p8WMa`16l@KegmxUCW7{ zBiI(C2@Bu3%A8C0ieH5d;@{yY_q}14#9g2&`%hhK)pp7N000000000000000;D7Q7 XKSy>t{peI500000NkvXXu0mjfY3T5G diff --git a/Tests/images/imagedraw_chord_zero_width.png b/Tests/images/imagedraw_chord_zero_width.png index c1c0058d766c76757ca24dc7503b64a66f288cdf..c8ebe39175c059e0ea2ed6f04c250a83f079acd6 100644 GIT binary patch delta 403 zcmV;E0c`%O1FZv)B!6^CL_t(|obB7oP6I&~*u9 zbiUN;58IexeKCB)UXEB_kKL`8Jl4g5TlLb$N*T3JFJY|2A%C0nQpHLiuR|~2TZik) zdr4yHrmF3=(`FqCd*#P63{%x>zs)+7^h%E9oWb8~*UdVZd+i-7yuG%M6~Z#!UbK delta 401 zcmV;C0dD@S1FHj&B!6;AL_t(|obA~`jsh_ZL{Y>2@7yeqdV@q0aFreN-vyHVb|xAT z0RR91000000002k&Jl4`@8Qy|cKO#p#T$C7W^eC@sBN#if62@1*7iPHzv5DbhV$i{ ztkrpGIA4zM_sg+c&6j+#ejJci^QDcIa@0P(3SPr6H0dRbm4AD}4!u;d6lW{%wZ}C) z)%KFak{+qBSAHzRVXAs^Wi6}^&UnGaxIZsW;%a`xIjRv3F_#wvW&JxfoR ztS3XSIk7_DYgDWd_nHcYvY>ZMzk zYq&rg%RgMw73GuV?4@4IcNXdIE}mof{$}aUWBcF*PgdfHnRj3C8fKkv*z0laizzGI vafg57B0i|rWupK90000000000{@Vj<&@D_Wddw*R015yANkvXXu0mjftPj3w diff --git a/Tests/images/imagedraw_pieslice.png b/Tests/images/imagedraw_pieslice.png index 2f8c091915ff0b92b268661484a53660e1abf136..41c786e77128cf8a3896338ff67113ed6f149843 100644 GIT binary patch delta 348 zcmeBT{?0T(rQXid#WAE}&f8n3g$^kQxLiE;|9^Uni|!-kHZEr4;O{!I8uzj`J909> zK|xexZG->U+ueHSUOp124h_01uDv^1&$_rLVr}{kR`va7|DWj2x*K#&-af*aZ~D^x zkE>?x*;H{l%T#-|V%eIrTjC^>GQ-xZY&2OKJ};tv)BCAf(|W2?yH!ueaVFn9DS0|0 z#xhybQ~GpLOlETA37PC4JB*G?^+m{bTve=Gv$22ela0re!q}5a6~fr(Z|n-`+TX1{ zf1})7OHZ-B>NcyJ!73dGES!qDs@|eCZPMmaAgzT6!-oSw5mm z%sczLsNOWcZ=q6l+DChqhW_S~@;<&-CvEA8jVAT+3zJ`F>o1Pftkc>1b(41LkA1~k o!-XvOO|-5{x2Y5Pgg&ebxsLQ06$2U_W%F@ delta 367 zcmV-#0g(Rp0*V8WB!5v!L_t(|obB4dj>0euMbVe}|9{RbszR%ws8Q|2_S~~%LBo{| zNUa0_00000000000JyEKKERd!=auzGU4McLy&rP5(=nHwIY+FcL%7+8|CxNf=X%%P zv47fDNBLrH&dDap8f!DTSMFGPkJyZrOdo4AXT*xc(v)6xVt?t~%S)+P((C)nMYUKv zmtF;9`7L9?SaMs&nz44qy~@V&SjMukJXDTl{>@c6mN{Y#_z$tnHnDgt+uy|Ev1}1* z0I>!TYe3jot;M^g88(&z_KF-!(Ru}srBJ=%$I_EtwxLtR(zE$83zj98BJ5+LSX$mI zS1b+nYE8D!aA>8ArG@jAF_!N1@(7wVmhR1$aj?9x^re^6o~DkakG=L+?1N>GrHlD; z8La}bbnIpRcj0syr}tgqb#tQ9hjKLav3LLg00000000000002^<`-z9H)g~J$2kB1 N002ovPDHLkV1jZbxNHCb diff --git a/Tests/images/imagedraw_pieslice_width.png b/Tests/images/imagedraw_pieslice_width.png index 3bd69222cf3ff22977e358bcb0fcc2c84c98e284..422d92f3bbabcd6c31728272a6217dafbae74fb7 100644 GIT binary patch delta 467 zcmV;^0WALT1MLHlB!9F?L_t(|obB7oj)O1^h2ga7`@eF#K&1$JNs4`JNB_5)1)792 zP6#B^8UO$Q000000001>+a3`k`S$RAds;tJmi>v3{)n<)|2PS}g5Z8Ih%U^4Dp;!7CT@%Nl( zbDKGb;d*b0W`FmVHLO*xm6!S0)<3y^#%I=t>vMQmTQmHB4*&oF00000004lwJpd)0IA^c5DUSdE002ov JPDHLkV1gKa;&lK3 delta 470 zcmV;{0V)3N1MmZoB!9O_L_t(|obB7&Zi6roMNy~9|Nk<1sDcQg2E$y=(7m2oDUHox zdwQuj%twkzTDmvpX>DzeGk>Sd}uG{bV#+GN~3mI~$MksrP=NPp_*YUj(pgDa4S!1})ttlHEUSY_DXs>7 zVO>c|ROB%1CsiOvr?9@usz4UIJi@wXQY~pUtSTU^(`6;MAex6IcZG($D{H8%)TcJ) ztO2%R-FXZ)=gLwaR-?p+p;oD?g(Y2#>0gEIS{7Gwl=@*UC4Z2zZOrhCmaWJlnrZjd4CeiqHI_z-=&cdpX;1u{{BNvFWGd%{9+Ui4|&})wN0r9vm53eR{YH3 z*rIt@YM)mu`;&Xtp;%l0Va_t+5mwfo$bIADR9W$PMQrizaq=%{TJ#HR^Tv8yRon@b z_-F98d4-0000000000000000Dx{DvkQ1;`TY>8sz1JG7qEQ3Bn%T}Wzw`VMOHpdE6%G! zzov-G)|X>nSp&7ZTRG75XcQ9e zfv8K?fT=8aZXMDil6Ava6`y3?!&K48vT_s7u{>Fwu}YCXb6FV%eHLx9Qsku{whjXz zYXDnWFSBlR4z{k3HH}Hu0P$r73fJ_X!g{EDO?w-WLRQ6l)|~X&c5`WgUunJAI@63v zE7C>{WEC@CFMmy%v=VU|Zk7esE)h}I#(OsB>ND@$W+wL2q0elLp_A1^bw8xhi@zxy zLz-o+EdM1YSF(aVz)b6rPeadesrypMiW4s5N<0>LpKv+!B9c`Xt{#ccQHi^49i-@F z`Gw2v1de*5YtLux+yK>})YGta(~ZXBY)aeo6UJvs_hxl#hvT*1LUrQh&+Q#WAE}&fD3yiw+s^u=+0l|Nrz$=X58Jp!f}aD%0lfW3Gep<`V0rn6O=3ycgB7HNt*QH@mimF(-BD z`C~GNQl93o4xF?-QSbiF6jlD@AO4BHegB%?b3B_~`b5emymnjjUJ>KV!P9r2NuEFN zXF==MNmuJTwHiEA=LT7dn;1_zWmWk0g+T2g)72L9xVGH6IVH2M;moxk+jk152dAx@ z9K@T}c;VEi_{b&7vreDdm~XprPX8WW+rvxcUJGB-F&5dR$A4r;i}!ZUcO8?SzCC>~ zb5-f$Fc{MR2eB_1IeV-;$G)et$9vbt-e#j%Q5Y^D9g7jr`uVSwdk?ic7M> z+NS)x6Wj6gTewu1)-@}ubBiZz`M*Kp(8{g}uj8Ck*L~*SX|`q6h2I|S_X8`HQxl8% lrfY%Q~loCID|0=m7u# diff --git a/Tests/images/imagedraw_pieslice_zero_width.png b/Tests/images/imagedraw_pieslice_zero_width.png index 4ca05158327fddd25e46ba9fc651f2e576cf9b29..d6ceb0b5a04d5ab60cb3e94dc371fe4c7754ecbc 100644 GIT binary patch delta 378 zcmbQtJe7HZN_~u{i(^Q|oVT|RavgHualIJw=YRY^TWdmIB)tvSvf9okJ>kf%dZVy&$|qA)uN*Iu>+PPgrsYwD zbbn}t-?ZGq;}P3BrUpo!{`O_%p%hgC?Sm%Mo=fIRoeqB5KkHIq!H>VCGb8T*`m8JS zdbZX5u#I+8wrkf}ZI26_)w4R0X`>KOugvL0^DpbZRRE3s6=8Qcv`)n*tVw0s;R4OR z#*UH=;W+dOQXZ9rTFf+2Cmcif9BPjrE#ktGw)j-ept&l-1%qj&S~El z=f7T1ZJV3^>j3NO4{V8_SyOV%OAK$G*mvah|F-`Ucix?=@2vhQc<$k8HAFzU9AW17 U4@tjf@(V2H>FVdQ&MBb@0Lpc_9{>OV delta 376 zcmbQrJehfdN_~{4i(^Q|oVPav`wkiKxCV-E{IA>I?Sv5#9 zz(Imla*v_gSG~MF+0)<4zlio-vr|eo?x(k3)TuMu&K&)Ba!c;Y=%SOe*Hp5(&1W*vKFxx_d5tBVeMlBLVPDU0@HoT~pZQ}bG6 z(Y~F@v6n<+46a3n*y&XObvzaSu=GHzVI<#?B96YRZ&i-HYd=|}FwMDUjh(t-jrydM zuP%6RWba$pT&NZHwxBai+%GIt|L!iJTXNU@c{pjE#{4yB;$Ch2G|%N%Zuq(RD(}w; z)iusE-NUNC=76C0K4I>AZGrv~AHI3Sy+7Wpsr{#X`#bP0l+XkKXEUth diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index bb1254a63..a3bac6c3c 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -196,13 +196,11 @@ def helper_chord(mode, bbox, start, end): def test_chord1(): for mode in ["RGB", "L"]: helper_chord(mode, BBOX1, 0, 180) - helper_chord(mode, BBOX1, 0.5, 180.4) def test_chord2(): for mode in ["RGB", "L"]: helper_chord(mode, BBOX2, 0, 180) - helper_chord(mode, BBOX2, 0.5, 180.4) def test_chord_width(): @@ -465,13 +463,13 @@ def helper_pieslice(bbox, start, end): def test_pieslice1(): - helper_pieslice(BBOX1, -90, 45) - helper_pieslice(BBOX1, -90.5, 45.4) + helper_pieslice(BBOX1, -92, 46) + helper_pieslice(BBOX1, -92.2, 46.2) def test_pieslice2(): - helper_pieslice(BBOX2, -90, 45) - helper_pieslice(BBOX2, -90.5, 45.4) + helper_pieslice(BBOX2, -92, 46) + helper_pieslice(BBOX2, -92.2, 46.2) def test_pieslice_width(): diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index cbecf652d..41c72efc2 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -119,7 +119,7 @@ class ImageDraw: fill = self.draw.draw_ink(fill) return ink, fill - def arc(self, xy, start, end, fill=None, width=0): + def arc(self, xy, start, end, fill=None, width=1): """Draw an arc.""" ink, fill = self._getink(fill) if ink is not None: diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 3eee5983a..7cfc49bb5 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -1150,6 +1150,30 @@ typedef struct { typedef void (*clip_ellipse_init)(clip_ellipse_state*, int32_t, int32_t, int32_t, float, float); +void debug_clip_tree(clip_node* root, int space) { + if (root == NULL) { + return; + } + if (root->type == CT_CLIP) { + int t = space; + while (t--) { + fputc(' ', stderr); + } + fprintf(stderr, "clip %+fx%+fy%+f > 0\n", root->a, root->b, root->c); + } else { + debug_clip_tree(root->l, space + 2); + int t = space; + while (t--) { + fputc(' ', stderr); + } + fprintf(stderr, "%s\n", root->type == CT_AND ? "and" : "or"); + debug_clip_tree(root->r, space + 2); + } + if (space == 0) { + fputc('\n', stderr); + } +} + // Resulting angles will satisfy 0 <= al < 360, al <= ar <= al + 360 void normalize_angles(float* al, float* ar) { if (*ar - *al >= 360) { @@ -1162,10 +1186,10 @@ void normalize_angles(float* al, float* ar) { } // An arc with caps orthogonal to the ellipse curve. -void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float _al, float _ar) { +void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) { if (a < b) { // transpose the coordinate system - arc_init(s, b, a, w, 90 - _ar, 90 - _al); + arc_init(s, b, a, w, 90 - ar, 90 - al); ellipse_init(&s->st, a, b, w); clip_tree_transpose(s->root); } else { @@ -1175,8 +1199,6 @@ void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float _al, s->head = NULL; s->node_count = 0; - int32_t al = round(_al), ar = round(_ar); - // building clipping tree, a lot of different cases if (ar == al + 360) { s->root = NULL; @@ -1191,25 +1213,12 @@ void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float _al, rc->a = a * sin(ar * M_PI / 180.0); rc->b = -b * cos(ar * M_PI / 180.0); rc->c = (b * b - a * a) * sin(ar * M_PI / 90.0) / 2.0; - if (al % 180 == 0 || ar % 180 == 0 || al == ar) { + if (fmod(al, 180) == 0 || fmod(ar, 180) == 0) { s->root = s->nodes + s->node_count++; s->root->l = lc; s->root->r = rc; s->root->type = ar - al < 180 ? CT_AND : CT_OR; - if (al == ar) { - lc = s->nodes + s->node_count++; - lc->l = lc->r = NULL; - lc->type = CT_CLIP; - lc->a = al == 0 ? 1 : al == 180 ? -1 : 0; - lc->b = al % 180 ? (al < 180 ? 1 : -1) : 0; - lc->c = 0; - rc = s->root; - s->root = s->nodes + s->node_count++; - s->root->l = lc; - s->root->r = rc; - s->root->type = CT_AND; - } - } else if ((al / 180 + ar / 180) % 2 == 1) { + } else if (((int)(al / 180) + (int)(ar / 180)) % 2 == 1) { s->root = s->nodes + s->node_count++; s->root->l = s->nodes + s->node_count++; s->root->l->l = s->nodes + s->node_count++; @@ -1226,8 +1235,8 @@ void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float _al, s->root->r->l->l = s->root->r->l->r = NULL; s->root->l->l->a = s->root->l->l->c = 0; s->root->r->l->a = s->root->r->l->c = 0; - s->root->l->l->b = (al / 180) % 2 == 0 ? 1 : -1; - s->root->r->l->b = (ar / 180) % 2 == 0 ? 1 : -1; + s->root->l->l->b = (int)(al / 180) % 2 == 0 ? 1 : -1; + s->root->r->l->b = (int)(ar / 180) % 2 == 0 ? 1 : -1; } else { s->root = s->nodes + s->node_count++; s->root->l = s->nodes + s->node_count++; @@ -1268,6 +1277,48 @@ void chord_line_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, flo s->root->r->c = 2 * w * sqrt(pow(s->root->l->a, 2.0) + pow(s->root->l->b, 2.0)) - s->root->l->c; } +// Pie side. +void pie_side_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float _) { + ellipse_init(&s->st, a, b, a + b + 1); + + s->head = NULL; + s->node_count = 0; + + double xl = a * cos(al * M_PI / 180.0); + double yl = b * sin(al * M_PI / 180.0); + double a1 = -yl; + double b1 = xl; + double c1 = w * sqrt(a1 * a1 + b1 * b1); + + s->root = s->nodes + s->node_count++; + s->root->type = CT_AND; + s->root->l = s->nodes + s->node_count++; + s->root->l->type = CT_AND; + + clip_node* cnode; + cnode = s->nodes + s->node_count++; + cnode->l = cnode->r = NULL; + cnode->type = CT_CLIP; + cnode->a = a1; + cnode->b = b1; + cnode->c = c1; + s->root->l->l = cnode; + cnode = s->nodes + s->node_count++; + cnode->l = cnode->r = NULL; + cnode->type = CT_CLIP; + cnode->a = -a1; + cnode->b = -b1; + cnode->c = c1; + s->root->l->r = cnode; + cnode = s->nodes + s->node_count++; + cnode->l = cnode->r = NULL; + cnode->type = CT_CLIP; + cnode->a = b1; + cnode->b = -a1; + cnode->c = 0; + s->root->r = cnode; +} + // A chord. void chord_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) { ellipse_init(&s->st, a, b, w); @@ -1384,12 +1435,13 @@ clipEllipseNew(Imaging im, int x0, int y0, int x1, int y1, int a = x1 - x0; int b = y1 - y0; - if (a < 0 || b < 0 || start == end) { + if (a < 0 || b < 0) { return 0; } clip_ellipse_state st; init(&st, a, b, width, start, end); + // debug_clip_tree(st.root, 0); int32_t X0, Y, X1; int next_code; while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) { @@ -1425,9 +1477,17 @@ chordLineNew(Imaging im, int x0, int y0, int x1, int y1, static int pieNew(Imaging im, int x0, int y0, int x1, int y1, float start, float end, - const void* ink_, int op) + const void* ink_, int width, int op) { - return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, x1 + y1 - x0 - y0, op, pie_init); + return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, pie_init); +} + +static int +pieSideNew(Imaging im, int x0, int y0, int x1, int y1, + float start, + const void* ink_, int width, int op) +{ + return clipEllipseNew(im, x0, y0, x1, y1, start, 0, ink_, width, op, pie_side_init); } int @@ -1483,38 +1543,26 @@ ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int fill, int width, int op) { - //fprintf(stderr, "P (%d %d) (%d %d) %f-%f %08X f%d w%d o%d\n", x0, y0, x1, y1, start, end, *(int*)ink, fill, width, op); + // fprintf(stderr, "P (%d %d) (%d %d) %f-%f %08X f%d w%d o%d\n", x0, y0, x1, y1, start, end, *(int*)ink, fill, width, op); normalize_angles(&start, &end); if (start + 360 == end) { - return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op); + return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); } if (start == end) { return 0; } if (fill) { - return pieNew(im, x0, y0, x1, y1, start, end, ink, op); + return pieNew(im, x0, y0, x1, y1, start, end, ink, x1 + y1 - x0 - y0, op); } else { - float xc = x0 + (x1 - x0) / 2.0, yc = y0 + (y1 - y0) / 2.0; - float al = start * M_PI / 180.0, ar = end * M_PI / 180.0; - int32_t xa = xc + (x1 - x0 - width) * cos(al) / 2, ya = yc + (y1 - y0 - width) * sin(al) / 2; - int32_t xb = xc + (x1 - x0 - width) * cos(ar) / 2, yb = yc + (y1 - y0 - width) * sin(ar) / 2; - int32_t xt, yt; - if (ImagingDrawWideLine(im, xc, yc, xa, ya, ink, width, op) < 0) { + if (pieSideNew(im, x0, y0, x1, y1, start, ink, width, op)) { return -1; } - if (ImagingDrawWideLine(im, xc, yc, xb, yb, ink, width, op) < 0) { + if (pieSideNew(im, x0, y0, x1, y1, end, ink, width, op)) { return -1; } - xt = xc - width / 2; - yt = yc - width / 2; - ellipseNew(im, xt, yt, xt + width, yt + width, ink, 1, 0, op); - xt = xa - width / 2; - yt = ya - width / 2; - ellipseNew(im, xt, yt, xt + width, yt + width, ink, 1, 0, op); - xt = xb - width / 2; - yt = yb - width / 2; - ellipseNew(im, xt, yt, xt + width, yt + width, ink, 1, 0, op); - return arcNew(im, x0, y0, x1, y1, start, end, ink, width, op); + float xc = round((x0 + x1 - width) / 2.0), yc = round((y0 + y1 - width) / 2.0); + ellipseNew(im, xc, yc, xc + width - 1, yc + width - 1, ink, 1, 0, op); + return pieNew(im, x0, y0, x1, y1, start, end, ink, width, op); } } From 5830a641ccc039652c77efccee854e3f12bc9adc Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Mon, 29 Jun 2020 23:16:06 +0300 Subject: [PATCH 13/15] Added more tests --- Tests/images/imagedraw_arc_high.png | Bin 0 -> 1349 bytes Tests/images/imagedraw_chord_too_fat.png | Bin 0 -> 519 bytes Tests/images/imagedraw_outline_chord_RGB.png | Bin 259 -> 267 bytes Tests/images/imagedraw_pieslice_wide.png | Bin 0 -> 1089 bytes Tests/test_imagechops.py | 8 ++-- Tests/test_imagedraw.py | 37 +++++++++++++++++++ src/libImaging/Draw.c | 5 +-- 7 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 Tests/images/imagedraw_arc_high.png create mode 100644 Tests/images/imagedraw_chord_too_fat.png create mode 100644 Tests/images/imagedraw_pieslice_wide.png diff --git a/Tests/images/imagedraw_arc_high.png b/Tests/images/imagedraw_arc_high.png new file mode 100644 index 0000000000000000000000000000000000000000..e3fb66cd0c058be00de696ac35f994c0ea465ea8 GIT binary patch literal 1349 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k2}mkgS)OEIVCC_2aSW-L^Y-rTyxR^ut`~Q_ z`2T;?F9ikDM=El?6%}`1Z*;gU_`GAziRpDUKwS`E-T#91Pr1zT?e+gERH9VGj=sO? zUs>gPh^zC?s;?fl%5kbkaxTR!nJ2jZ$-f`}Cw~u4{p!5nan*h8OAfD3om`*XS;ezf zTYY(g=l1zIs!s%U+I!WbZ=Ps=y6tI-wrK8#Ip(u-o(g{0!6?kpEPBa4Bg%6{ljx3v zDca1Q8xCzZt6_Z5!^-(PLG`7W=QP7QVj|rhQd) z(mWeioPKlGXP0^Fr?Sgko91k+yIGwSIMH`Qp4rsx7kAFobz8OnN8eTnsb68bYzKE; z=GQy=)Nflx)VW92Vo|%Kb+(sn{Tps~af?lB>6*741vj)FhHW_YwV{Z6UAM8AN>Wp4 z@f<_m&$s!*E^Z7ex%rf(L}zlH^JT9-prl!IXw`x#aW$g%WKS2g81fn?i#R2zNAS9N zHhX)V+nPL+Uw>I*RF9KK3}4u6#Y@keiax!LShVq|$(hbTj?V0ZS!Q;#V*RvU+HH8% zCaG1rcE|1QJF3Kg&Y!nQ^5RC0$@{k*RPx!V$D;i|aJs?Mi%XIPSLWEvOWydJ`D25K z(CUMy>JK<+mFRc~=y;rHK6Hxw+*-PAYnI%a@cVMVJ#KG* zcO~m(kL%q+(e`pkP(1yn(Vd?5S|-c&ILL-0SsSGOC*AmQ>CD>epit>gu#^$C+`0bq zN>N$uiH|LA-BA>eyHsf-m#g9U6ahfsDlJ&I(-`xwI zyVI+=Uk7UQz1PZo{O)nohC}_kwk?@%yeAA83qM%BO{Ajy=CEzuC9}p%_TED$wd-Ah z9Guq^W^R8pt@uO8-Z1NfSr;DO)l1A-E73aP1K+en=R6yB|E1YmCD&Y%y;u3sIQytZ zi;KespIb%0Eh|<MC@vOzbZt&i==t)VpZsSQ*RH)(d&KU% z?ezF(QIUOzTJ|bFoaDzTp0<05PP&dsvdo$g^Q2Wr_jql!`F!+7`5Kk4UYmOEt41zK zk_09nk0#MNtBa!p99Bnf7`n3Fzw3Yk6xQ! zD_u49xW*IhbJw=6oO)TZd;P@N>x;wc+RHDhntESd>A04u?C7hra=OZ2UGGM*+T literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_chord_too_fat.png b/Tests/images/imagedraw_chord_too_fat.png new file mode 100644 index 0000000000000000000000000000000000000000..2021202fe790e1af564ad1ce5728ae6947daa908 GIT binary patch literal 519 zcmV+i0{H!jP)h%_XMrUW1H%mS=<4pe+iXThh2k z5LVNFf2pxfrLJa$hY`)39F_aDTKjV^Q3G9tOXa~mV2yaQV4*xGT&G$ zX9d?-i>2TfYrPcwVwvO;%VbvYh-D=OcUXo}@P%b71z%X^xxq4@6}(_ENWldbi4;Xf1zRj%QUHFe53t9~v}Lxenx9}| z%jS^fUyGy7U|CM3VECHli^(Sa25)U*Qc5YMlu}A5rIb=?UB5LjK8W@%4{iVe002ov JPDHLkV1jrF=1>3t literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_outline_chord_RGB.png b/Tests/images/imagedraw_outline_chord_RGB.png index 9e9cb5af006f84b806867b586948cd1b21e668c3..3c71312c75d0de08b191e0b361ed1a72a72825c4 100644 GIT binary patch delta 239 zcmZo>>SmgtQh&+Q#WAE}&fA-ge1{BpSPmTe|DQ+voJZSM*_Fop9dnQRMis^~pnwYx z1!G=|#%OD8J(->{H(DU9aRXON=mZ|^%E%`yyNx1Q4`~U6Ia;i_+_yHzUTw94{58wN zqVJESh;C^qJycNoC1DfSu0ywE*DZV1J9%ry)bRMb_l_6Xt-sdwHvL*k+}*8ed_QLH zVOzy>x9zU=%%8T;;$(k)Gyb+jKPK{^ePi41_-grI#+#<@o4((tt|MpTV`00;cKcY$ f1Exb9d0_8e#?&@ew^dBcRv-~iS3j3^P6xE3qsd8&t4654o`o3c6H|WV}^I8CWPPLcQ2B4Z{k_=@Yj8B=bFw8{~lMIp840= zzPM*+dD#8RV{$**w6$+1g-d-tuT&ZP=G%{V{a4JwY7R|58D+nu%xfXUbqDtDW$gLI U=J>v*Yz9cg)78&qol`;+0KsZ#pa1{> diff --git a/Tests/images/imagedraw_pieslice_wide.png b/Tests/images/imagedraw_pieslice_wide.png new file mode 100644 index 0000000000000000000000000000000000000000..44687478836b61cc3068427f50f88f5d2b877e05 GIT binary patch literal 1089 zcmV-H1it%;P) z%aZFL3Hu1^9#RA%f^wfX;L3k&paeBZqrf$_(Z|ZFk2PO zS~If%KFKgL%t{D@c5Fm|PgYnMrf)*qf>sCk0eq?e!$uL_jf1b@94g(Y@CsmSp4qF11nWD5eNvcS}fSejIf=LnR_ z2uCkU(Vt-nSXBZ};3!20@^N~DQd>YyUHx*sj0LMILJnoW+*j?$%|oe8p{1^Gh3+#J ztOCT~ek*h%7OVnK6mE^53N>AV^hg7J6^o`Qtf~Yd+@B?W`X9@>0lUF>i}_OstIEO* z{aY+30|t!T`v2o!Uem+54Wu;CT6@iJP1OPNrDl{|9v?F^mI+9E1vpyLe^-!iA%vv0 zB4e3=v=^JS8A}h$)6o55XUa$_hUqdl)tBrpRso-9ofLm5# z4Jmi0i@Vho%kt0rP}16QsD*T+9{^n}GZqpOc)<`0-kd1@2W!MrEZig>uxbP*Ncq63 zHl85;J&>+Cz#$Q}*9swxC;N&8Z~P#v-=`G|-oz&4A~d9lOx!*1@TLH7`*3mz7(?8K z#m-pp2GDpfLI*k>8^na1t{QuHL!`sm!Bm)`KZ!+qHz3@q2O;Q}64#6cx4;#?RZYIp z4{jAf8t5Cb$nW`vTf3upaq@k}f?GiOI__R&93W5>i^C~;|FJm7_8@ibg3*yD<5qA; zQx!}P02@CQGEcKZ>dJ}rv2r*FvpfqA!hC?Edv_zk(Y+6F*!U)dI&Ay}^h%7sQsOGD zX?6uduT@B&t7O&W4{<6%pw~O^I3%T3%~oC zv9AStOXj(v?;ZF_n-S*4Kd)-DHipdUX6i1NWigLt+oH`yGkaZgg|Xu7p=xwId6o8{&%v!)%EV)CgmE1H&|gie;$5oPcB$!p1M`z}~>HPvVCy{s^zs^ekPR z(hc~;-jbz`P3pnlkXNhead = NULL; s->node_count = 0; + normalize_angles(&al, &ar); // building clipping tree, a lot of different cases if (ar == al + 360) { @@ -1494,7 +1495,6 @@ int ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1, const void* ink, int fill, int width, int op) { - //fprintf(stderr, "E (%d %d) (%d %d) --- %08X f%d w%d o%d\n", x0, y0, x1, y1, *(int*)ink, fill, width, op); return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); } @@ -1502,7 +1502,6 @@ int ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int width, int op) { - //fprintf(stderr, "A (%d %d) (%d %d) %f-%f %08X f- w%d o%d\n", x0, y0, x1, y1, start, end, *(int*)ink, width, op); normalize_angles(&start, &end); if (start + 360 == end) { return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, 0, width, op); @@ -1519,7 +1518,6 @@ ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int fill, int width, int op) { - //fprintf(stderr, "C (%d %d) (%d %d) %f-%f %08X f%d w%d o%d\n", x0, y0, x1, y1, start, end, *(int*)ink, fill, width, op); normalize_angles(&start, &end); if (start + 360 == end) { return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op); @@ -1543,7 +1541,6 @@ ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int fill, int width, int op) { - // fprintf(stderr, "P (%d %d) (%d %d) %f-%f %08X f%d w%d o%d\n", x0, y0, x1, y1, start, end, *(int*)ink, fill, width, op); normalize_angles(&start, &end); if (start + 360 == end) { return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); From 88651c334f3b17daa99056dff1b5d30c297cb8f2 Mon Sep 17 00:00:00 2001 From: Stanislau Tsitsianok Date: Mon, 29 Jun 2020 23:58:58 +0300 Subject: [PATCH 14/15] Try to fix CI --- Tests/test_imagedraw.py | 10 ++++++---- src/libImaging/Draw.c | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 2f0584c66..615ef56a3 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -165,7 +165,7 @@ def test_arc_width_non_whole_angle(): def test_arc_high(): # Arrange - im = Image.new("RGB", (200, 200)); + im = Image.new("RGB", (200, 200)) draw = ImageDraw.Draw(im) # Act @@ -252,7 +252,7 @@ def test_chord_zero_width(): def test_chord_too_fat(): # Arrange - im = Image.new("RGB", (100, 100)); + im = Image.new("RGB", (100, 100)) draw = ImageDraw.Draw(im) # Act @@ -401,7 +401,9 @@ def test_ellipse_various_sizes(): def test_ellipse_various_sizes_filled(): im = ellipse_various_sizes_helper(True) - with Image.open("Tests/images/imagedraw_ellipse_various_sizes_filled.png") as expected: + with Image.open( + "Tests/images/imagedraw_ellipse_various_sizes_filled.png" + ) as expected: assert_image_equal(im, expected) @@ -529,7 +531,7 @@ def test_pieslice_zero_width(): def test_pieslice_wide(): # Arrange - im = Image.new("RGB", (200, 100)); + im = Image.new("RGB", (200, 100)) draw = ImageDraw.Draw(im) # Act diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 9e93b8366..87e25334c 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -1050,10 +1050,10 @@ int clip_tree_do_clip(clip_node* root, int32_t x0, int32_t y, int32_t x1, event_ // X of intersection double ix = - (B * y + C) / A; if (A * x0 + B * y + C < eps) { - x0 = round(fmax(x0, ix)); + x0 = lround(fmax(x0, ix)); } if (A * x1 + B * y + C < eps) { - x1 = round(fmin(x1, ix)); + x1 = lround(fmin(x1, ix)); } } if (x0 <= x1) { @@ -1557,7 +1557,7 @@ ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, if (pieSideNew(im, x0, y0, x1, y1, end, ink, width, op)) { return -1; } - float xc = round((x0 + x1 - width) / 2.0), yc = round((y0 + y1 - width) / 2.0); + int xc = lround((x0 + x1 - width) / 2.0), yc = lround((y0 + y1 - width) / 2.0); ellipseNew(im, xc, yc, xc + width - 1, yc + width - 1, ink, 1, 0, op); return pieNew(im, x0, y0, x1, y1, start, end, ink, width, op); } From 17d83d6a7c619e87fab84c59860989772fdcdb7d Mon Sep 17 00:00:00 2001 From: Stanislau T <44941959+xtsm@users.noreply.github.com> Date: Fri, 11 Sep 2020 19:51:57 +0300 Subject: [PATCH 15/15] Fix comment grammar Co-authored-by: Hugo van Kemenade --- src/libImaging/Draw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 87e25334c..a2b2b10f3 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -862,7 +862,7 @@ int8_t quarter_next(quarter_state* s, int32_t* ret_x, int32_t* ret_y) { if (s->cx == s->ex && s->cy == s->ey) { s->finished = 1; } else { - // bresenham's algorithm, possible optimization: only consider 2 of 3 + // Bresenham's algorithm, possible optimization: only consider 2 of 3 // next points depending on current slope int32_t nx = s->cx; int32_t ny = s->cy + 2;