1 /* -*- Mode: C; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // -------------------------------------------------------------------------*/
12 #include "sjme/util.h"
13 #include "lib/scritchui/scritchui.h"
14 #include "lib/scritchui/scritchuiPencil.h"
15 #include "lib/scritchui/scritchuiTypes.h"
16 #include "lib/scritchui/core/coreRaster.h"
17 #include "sjme/debug.h"
18 #include "sjme/fixed.h"
20 sjme_errorCode
sjme_scritchpen_corePrim_mapColorFromRGB(
21 sjme_attrInNotNull sjme_scritchui_pencil g
,
22 sjme_attrInValue sjme_jint argb
,
23 sjme_attrOutNotNull sjme_scritchui_pencilColor
* outColor
)
25 sjme_jint v
, aa
, rr
, gg
, bb
, ii
;
26 sjme_jint i
, numCol
, d
, bestCol
, bestColScore
, thisColScore
;
27 sjme_jint pargb
, prr
, pgg
, pbb
;
28 sjme_jint mrr
, mgg
, mbb
;
29 const sjme_jint
* colors
;
31 if (g
== NULL
|| outColor
== NULL
)
32 return SJME_ERROR_NULL_ARGUMENTS
;
34 /* Set base color properties. */
35 aa
= (argb
>> 24) & 0xFF;
37 rr
= (argb
>> 16) & 0xFF;
39 gg
= (argb
>> 8) & 0xFF;
44 /* Find closest indexed color. */
46 numCol
= g
->palette
.numColors
;
47 if (g
->palette
.colors
!= NULL
&& numCol
> 0)
52 /* Determine the most important color channel. */
53 mrr
= (rr
>= gg
&& rr
>= bb
? 1 : 2);
54 mgg
= (gg
>= rr
&& gg
>= bb
? 1 : 2);
55 mbb
= (bb
>= rr
&& bb
>= gg
? 1 : 2);
57 /* Start with a horrible color score. */
59 bestColScore
= 134217728;
61 /* Find exact color match? */
62 colors
= g
->palette
.colors
;
63 for (i
= 0; i
< numCol
; i
++)
66 pargb
= colors
[i
] & 0xFFFFFF;
73 /* Get original RGB value. */
74 prr
= (pargb
>> 16) & 0xFF;
75 pgg
= (pargb
>> 8) & 0xFF;
78 /* Calculate this color score, use if it is better. */
79 thisColScore
= (abs(prr
- rr
) * mrr
) +
80 (abs(pgg
- gg
) * mgg
) +
81 (abs(pbb
- bb
) * mbb
);
82 if (thisColScore
< bestColScore
)
85 bestColScore
= thisColScore
;
89 /* If no exact color was found, use the best scoring one. */
94 /* Determine raw pixel color. */
95 switch (g
->pixelFormat
)
97 case SJME_GFX_PIXEL_FORMAT_INT_ARGB8888
:
101 case SJME_GFX_PIXEL_FORMAT_INT_BGRA8888
:
102 v
= (bb
<< 24) | (gg
<< 16) | (rr
<< 8) | aa
;
105 case SJME_GFX_PIXEL_FORMAT_INT_BGRX8888
:
106 v
= (bb
<< 24) | (gg
<< 16) | (rr
<< 8) | 0xFF;
109 case SJME_GFX_PIXEL_FORMAT_INT_BGR888
:
110 v
= 0xFF000000 | (bb
<< 16) | (gg
<< 8) | (rr
);
113 case SJME_GFX_PIXEL_FORMAT_INT_RGBX8888
:
114 v
= (rr
<< 24) | (gg
<< 16) | (bb
<< 8) | 0xFF;
117 case SJME_GFX_PIXEL_FORMAT_INT_RGB888
:
118 v
= argb
| 0xFF000000;
121 case SJME_GFX_PIXEL_FORMAT_BYTE3_RGB888
:
122 v
= (rr
<< 16) | (gg
<< 8) | bb
;
125 case SJME_GFX_PIXEL_FORMAT_BYTE3_BGR888
:
126 v
= (rr
) | (gg
<< 8) | (bb
<< 16);
129 case SJME_GFX_PIXEL_FORMAT_SHORT_ARGB4444
:
130 v
= (((aa
>> 4) & 0xF) << 12) |
131 (((rr
>> 4) & 0xF) << 8) |
132 (((gg
>> 4) & 0xF) << 4) |
136 case SJME_GFX_PIXEL_FORMAT_SHORT_RGB565
:
137 v
= (((rr
>> 3) & 0x1F) << 11) |
138 (((gg
>> 2) & 0x3F) << 5) |
142 case SJME_GFX_PIXEL_FORMAT_SHORT_RGB555
:
143 v
= (((rr
>> 3) & 0x1F) << 10) |
144 (((gg
>> 3) & 0x1F) << 5) |
148 case SJME_GFX_PIXEL_FORMAT_SHORT_ABGR1555
:
149 v
= (((aa
>> 7) & 0x1) << 15) |
150 (((bb
>> 3) & 0x1F) << 10) |
151 (((gg
>> 3) & 0x1F) << 5) |
155 case SJME_GFX_PIXEL_FORMAT_SHORT_INDEXED65536
:
159 case SJME_GFX_PIXEL_FORMAT_BYTE_INDEXED256
:
163 case SJME_GFX_PIXEL_FORMAT_PACKED_INDEXED4
:
167 case SJME_GFX_PIXEL_FORMAT_PACKED_INDEXED2
:
171 case SJME_GFX_PIXEL_FORMAT_PACKED_INDEXED1
:
176 /* Store raw colors. */
179 outColor
->argb
= argb
;
182 return SJME_ERROR_NONE
;
185 sjme_errorCode
sjme_scritchpen_corePrim_mapColorFromRaw(
186 sjme_attrInNotNull sjme_scritchui_pencil g
,
187 sjme_attrInValue sjme_jint v
,
188 sjme_attrOutNotNull sjme_scritchui_pencilColor
* outColor
)
190 sjme_jint numCol
, aa
, rr
, gg
, bb
, argb
;
192 if (g
== NULL
|| outColor
== NULL
)
193 return SJME_ERROR_NULL_ARGUMENTS
;
195 /* If there is a palette, try using it to get a color. */
196 numCol
= g
->palette
.numColors
;
197 if (g
->palette
.colors
!= NULL
&& numCol
> 0)
199 if (v
> 0 && v
< numCol
)
200 return sjme_scritchpen_corePrim_mapColorFromRGB(g
,
201 g
->palette
.colors
[v
], outColor
);
203 /* Invalid color, render to black or close to it. */
204 return sjme_scritchpen_corePrim_mapColorFromRGB(g
,
208 /* Initial map to black. */
214 /* Recover raw pixel color. */
215 switch (g
->pixelFormat
)
217 case SJME_GFX_PIXEL_FORMAT_INT_ARGB8888
:
218 aa
= (v
>> 24) & 0xFF;
219 rr
= (v
>> 16) & 0xFF;
220 gg
= (v
>> 8) & 0xFF;
224 case SJME_GFX_PIXEL_FORMAT_INT_BGRA8888
:
226 rr
= (v
>> 8) & 0xFF;
227 gg
= (v
>> 16) & 0xFF;
228 bb
= (v
>> 24) & 0xFF;
231 case SJME_GFX_PIXEL_FORMAT_INT_BGRX8888
:
233 rr
= (v
>> 8) & 0xFF;
234 gg
= (v
>> 16) & 0xFF;
235 bb
= (v
>> 24) & 0xFF;
238 case SJME_GFX_PIXEL_FORMAT_INT_BGR888
:
239 case SJME_GFX_PIXEL_FORMAT_BYTE3_BGR888
:
242 gg
= (v
>> 8) & 0xFF;
243 bb
= (v
>> 16) & 0xFF;
246 case SJME_GFX_PIXEL_FORMAT_INT_RGB888
:
247 case SJME_GFX_PIXEL_FORMAT_BYTE3_RGB888
:
249 rr
= (v
>> 16) & 0xFF;
250 gg
= (v
>> 8) & 0xFF;
254 case SJME_GFX_PIXEL_FORMAT_INT_RGBX8888
:
256 rr
= (v
>> 24) & 0xFF;
257 gg
= (v
>> 16) & 0xFF;
258 bb
= (v
>> 8) & 0xFF;
261 case SJME_GFX_PIXEL_FORMAT_SHORT_ARGB4444
:
262 aa
= ((v
>> 12) & 0xF);
264 rr
= ((v
>> 8) & 0xF);
266 gg
= ((v
>> 4) & 0xF);
272 case SJME_GFX_PIXEL_FORMAT_SHORT_RGB565
:
274 rr
= ((v
>> 11) & 0x1F) << 3;
275 gg
= ((v
>> 4) & 0x3F) << 2;
276 bb
= ((v
) & 0x1F) << 3;
279 case SJME_GFX_PIXEL_FORMAT_SHORT_RGB555
:
281 rr
= ((v
>> 10) & 0x1F) << 3;
282 gg
= ((v
>> 5) & 0x1F) << 3;
283 bb
= ((v
) & 0x1F) << 3;
286 case SJME_GFX_PIXEL_FORMAT_SHORT_ABGR1555
:
288 v
= (((aa
>> 7) & 0x1) << 15) |
289 (((bb
>> 3) & 0x1F) << 10) |
290 (((gg
>> 3) & 0x1F) << 5) |
295 /* Map back to normalize. */
296 argb
= (aa
<< 24) | (rr
<< 16) | (gg
<< 8) | bb
;
297 outColor
->argb
= argb
;
298 return sjme_scritchpen_corePrim_mapColorFromRGB(g
, argb
, outColor
);
301 sjme_errorCode
sjme_scritchpen_corePrim_mapColor(
302 sjme_attrInNotNull sjme_scritchui_pencil g
,
303 sjme_attrInValue sjme_jboolean fromRaw
,
304 sjme_attrInValue sjme_jint inRgbOrRaw
,
305 sjme_attrOutNotNull sjme_scritchui_pencilColor
* outColor
)
307 if (g
== NULL
|| outColor
== NULL
)
308 return SJME_ERROR_NULL_ARGUMENTS
;
310 /* Otherwise, use our own colormapping code. */
312 return sjme_scritchpen_corePrim_mapColorFromRaw(g
,
313 inRgbOrRaw
, outColor
);
314 return sjme_scritchpen_corePrim_mapColorFromRGB(g
,
315 inRgbOrRaw
, outColor
);
318 sjme_errorCode
sjme_scritchpen_coreUtil_applyAnchor(
319 sjme_attrInValue sjme_jint anchor
,
320 sjme_attrInValue sjme_jint x
,
321 sjme_attrInValue sjme_jint y
,
322 sjme_attrInPositive sjme_jint w
,
323 sjme_attrInPositive sjme_jint h
,
324 sjme_attrInValue sjme_jint baseline
,
325 sjme_attrOutNotNull sjme_jint
* outX
,
326 sjme_attrOutNotNull sjme_jint
* outY
)
328 if (outX
== NULL
|| outY
== NULL
)
329 return SJME_ERROR_NULL_ARGUMENTS
;
331 /* Cannot be negative. */
333 return SJME_ERROR_INVALID_ARGUMENT
;
335 /* Horizontal anchoring. */
336 if ((anchor
& SJME_SCRITCHUI_ANCHOR_LEFT
) == 0)
338 if ((anchor
& SJME_SCRITCHUI_ANCHOR_HCENTER
) != 0)
340 else if ((anchor
& SJME_SCRITCHUI_ANCHOR_RIGHT
) != 0)
344 /* Vertical anchoring. */
345 if ((anchor
& SJME_SCRITCHUI_ANCHOR_TOP
) == 0)
347 if ((anchor
& SJME_SCRITCHUI_ANCHOR_VCENTER
) != 0)
349 else if ((anchor
& SJME_SCRITCHUI_ANCHOR_BOTTOM
) != 0)
351 else if ((anchor
& SJME_SCRITCHUI_ANCHOR_BASELINE
) != 0)
358 return SJME_ERROR_NONE
;
361 sjme_errorCode
sjme_scritchpen_coreUtil_applyRotateScale(
362 sjme_attrOutNotNull sjme_scritchui_pencilMatrix
* outMatrix
,
363 sjme_attrInValue sjme_scritchui_pencilTranslate inTrans
,
364 sjme_attrInPositive sjme_jint wSrc
,
365 sjme_attrInPositive sjme_jint hSrc
,
366 sjme_attrInPositive sjme_jint wDest
,
367 sjme_attrInPositive sjme_jint hDest
)
369 sjme_scritchui_pencilMatrix result
;
370 sjme_fixed scaleX
, scaleY
, temp
;
373 if (outMatrix
== NULL
)
374 return SJME_ERROR_NULL_ARGUMENTS
;
377 memset(&result
, 0, sizeof(result
));
379 /* Perform total image scaling first. */
380 scaleX
= sjme_fixed_fraction(wSrc
, wDest
);
381 scaleY
= sjme_fixed_fraction(hSrc
, hDest
);
383 /* This is simple enough to calculate, is just the destination. */
387 /* Determine the transformation functions to use. There are just three */
388 /* primitive transformation functions: flip horizontally, then */
389 /* flip vertically, then rotate 90 degrees clockwise. This handles */
390 /* every transformation which fill every single bit. */
393 /* These bits represent the stuff to do! == 0b9VH; */
394 case SJME_SCRITCHUI_TRANS_NONE
: xform
= 0b000; break;
395 case SJME_SCRITCHUI_TRANS_MIRROR
: xform
= 0b001; break;
396 case SJME_SCRITCHUI_TRANS_MIRROR_ROT180
: xform
= 0b010; break;
397 case SJME_SCRITCHUI_TRANS_ROT180
: xform
= 0b011; break;
398 case SJME_SCRITCHUI_TRANS_ROT90
: xform
= 0b100; break;
399 case SJME_SCRITCHUI_TRANS_MIRROR_ROT90
: xform
= 0b101; break;
400 case SJME_SCRITCHUI_TRANS_MIRROR_ROT270
: xform
= 0b110; break;
401 case SJME_SCRITCHUI_TRANS_ROT270
: xform
= 0b111; break;
402 /* These bits represent the stuff to do! == 0b9VH; */
405 /* Start with this. */
406 result
.x
.wx
= scaleX
;
407 result
.y
.zy
= scaleY
;
409 /* Mirror horizontally? */
410 if ((xform
& 0b001) != 0)
411 result
.x
.wx
= -result
.x
.wx
;
413 /* Mirror vertically? */
414 if ((xform
& 0b010) != 0)
415 result
.y
.zy
= -result
.y
.zy
;
417 /* Rotate 90 degrees clockwise */
418 /* Thanks to jercos for helping out with the matrix math! */
419 /* The math here has been simplified to remove constants and otherwise. */
420 if ((xform
& 0b100) != 0)
423 result
.x
.wx
= result
.x
.zy
;
427 result
.y
.wx
= result
.y
.zy
;
432 memmove(outMatrix
, &result
, sizeof(result
));
433 return SJME_ERROR_NONE
;
436 sjme_errorCode
sjme_scritchpen_coreUtil_applyTranslate(
437 sjme_attrInNotNull sjme_scritchui_pencil g
,
438 sjme_attrInOutNotNull sjme_jint
* x
,
439 sjme_attrInOutNotNull sjme_jint
* y
)
441 if (g
== NULL
|| x
== NULL
|| y
== NULL
)
442 return SJME_ERROR_NULL_ARGUMENTS
;
444 /* Apply translation. */
445 (*x
) += g
->state
.translate
.x
;
446 (*y
) += g
->state
.translate
.y
;
449 return SJME_ERROR_NONE
;
452 sjme_errorCode
sjme_scritchpen_core_mapColor(
453 sjme_attrInNotNull sjme_scritchui_pencil g
,
454 sjme_attrInValue sjme_jboolean fromRaw
,
455 sjme_attrInValue sjme_jint inRgbOrRaw
,
456 sjme_attrOutNotNull sjme_scritchui_pencilColor
* outColor
)
458 if (g
== NULL
|| outColor
== NULL
)
459 return SJME_ERROR_NULL_ARGUMENTS
;
461 /* Use primitive function. */
462 return g
->prim
.mapColor(g
, fromRaw
, inRgbOrRaw
, outColor
);
465 sjme_errorCode
sjme_scritchpen_core_setAlphaColor(
466 sjme_attrInNotNull sjme_scritchui_pencil g
,
467 sjme_attrInValue sjme_jint argb
)
469 sjme_errorCode error
;
470 sjme_scritchui_pencilColor
* target
;
473 return SJME_ERROR_NULL_ARGUMENTS
;
475 /* Map color natively, if possible. */
476 target
= &g
->state
.color
;
477 if (sjme_error_is(error
= g
->prim
.mapColor(g
,
478 SJME_JNI_FALSE
, argb
, target
)))
479 return sjme_error_default(error
);
481 /* Is alpha applicable? */
482 /* Note that if we cannot read from the source buffer, we cannot */
483 /* apply alpha correctly so we just ignore it. */
484 g
->state
.applyAlpha
= (target
->a
!= 0xFF &&
485 g
->prim
.rawScanGet
!= NULL
&&
486 g
->state
.blending
!= SJME_SCRITCHUI_PENCIL_BLEND_SRC
);
489 if (g
->impl
->setAlphaColor
!= NULL
)
490 return g
->impl
->setAlphaColor(g
, argb
);
491 return SJME_ERROR_NONE
;
494 sjme_errorCode
sjme_scritchpen_core_setBlendingMode(
495 sjme_attrInNotNull sjme_scritchui_pencil g
,
496 sjme_attrInRange(0, SJME_NUM_SCRITCHUI_PENCIL_BLENDS
)
497 sjme_scritchui_pencilBlendingMode mode
)
499 sjme_scritchui_pencilColor
* color
;
502 return SJME_ERROR_NULL_ARGUMENTS
;
504 if (mode
< 0 || mode
>= SJME_NUM_SCRITCHUI_PENCIL_BLENDS
)
505 return SJME_ERROR_INVALID_ARGUMENT
;
507 /* Source blending cannot be used if there is no alpha channel. */
508 if (!g
->hasAlpha
&& mode
== SJME_SCRITCHUI_PENCIL_BLEND_SRC
)
509 return SJME_ERROR_INVALID_ARGUMENT
;
512 g
->state
.blending
= mode
;
514 /* Set color again, to reset alpha blending state. */
515 color
= &g
->state
.color
;
516 g
->api
->setAlphaColor(g
,
517 (color
->a
<< 24) | (color
->r
<< 16) | (color
->g
<< 8) |
521 if (g
->impl
->setBlendingMode
!= NULL
)
522 return g
->impl
->setBlendingMode(g
, mode
);
523 return SJME_ERROR_NONE
;
526 sjme_errorCode
sjme_scritchpen_core_setClip(
527 sjme_attrInNotNull sjme_scritchui_pencil g
,
528 sjme_attrInValue sjme_jint x
,
529 sjme_attrInValue sjme_jint y
,
530 sjme_attrInPositive sjme_jint w
,
531 sjme_attrInPositive sjme_jint h
)
533 sjme_scritchui_rect
* rect
;
534 sjme_scritchui_line
* line
;
538 return SJME_ERROR_NULL_ARGUMENTS
;
540 /* Translate coordinates. */
541 sjme_scritchpen_coreUtil_applyTranslate(g
, &x
, &y
);
543 /* Minimum bounds. */
549 /* Get actual coordinates of clip end. */
553 /* If the clip is negative, normalize to zero. */
559 /* If the clip exceeds the buffer bounds, clip it. */
565 /* Translate back. */
570 rect
= &g
->state
.clip
;
576 /* Set end coordinates. */
577 line
= &g
->state
.clipLine
;
583 /* Forward to native call. */
584 if (g
->impl
->setClip
!= NULL
)
585 return g
->impl
->setClip(g
, x
, y
, w
, h
);
586 return SJME_ERROR_NONE
;
589 sjme_errorCode
sjme_scritchpen_core_setDefaultFont(
590 sjme_attrInNotNull sjme_scritchui_pencil g
)
592 sjme_errorCode error
;
595 return SJME_ERROR_NULL_ARGUMENTS
;
597 /* Reset to use the default font. */
598 g
->state
.font
= g
->defaultFont
;
601 return SJME_ERROR_NONE
;
604 sjme_errorCode
sjme_scritchpen_core_setParametersFrom(
605 sjme_attrInNotNull sjme_scritchui_pencil g
,
606 sjme_attrInNotNull sjme_scritchui_pencil from
)
608 sjme_errorCode error
;
610 if (g
== NULL
|| from
== NULL
)
611 return SJME_ERROR_NONE
;
613 /* Initially successful. */
614 error
= SJME_ERROR_NONE
;
616 /* Remove any current translation. */
617 error
|= g
->api
->translate(g
,
618 -g
->state
.translate
.x
, -g
->state
.translate
.y
);
620 /* Copy all basic parameters. */
621 error
|= g
->api
->setAlphaColor(g
, from
->state
.color
.argb
);
622 error
|= g
->api
->setClip(g
, from
->state
.clip
.s
.x
, from
->state
.clip
.s
.y
,
623 from
->state
.clip
.d
.width
, from
->state
.clip
.d
.height
);
624 error
|= g
->api
->setStrokeStyle(g
, from
->state
.stroke
);
626 /* We can only copy the blending mode if we have alpha support. */
628 error
|= g
->api
->setBlendingMode(g
,
629 SJME_SCRITCHUI_PENCIL_BLEND_SRC_OVER
);
631 error
|= g
->api
->setBlendingMode(g
, from
->state
.blending
);
633 /* If the other has no font, then just set the default. */
634 if (from
->state
.font
== NULL
)
635 error
|= g
->api
->setDefaultFont(g
);
637 error
|= g
->api
->setFont(g
, from
->state
.font
);
639 /* Re-translate to target coordinate system. */
640 error
|= g
->api
->translate(g
,
641 from
->state
.translate
.x
, from
->state
.translate
.y
);
643 /* Any resultant error? */
647 sjme_errorCode
sjme_scritchpen_core_setStrokeStyle(
648 sjme_attrInNotNull sjme_scritchui_pencil g
,
649 sjme_attrInRange(0, SJME_NUM_SCRITCHUI_PENCIL_STROKES
)
650 sjme_scritchui_pencilStrokeMode style
)
653 return SJME_ERROR_NULL_ARGUMENTS
;
655 if (style
< 0 || style
>= SJME_NUM_SCRITCHUI_PENCIL_STROKES
)
656 return SJME_ERROR_INVALID_ARGUMENT
;
658 /* Set stroke mode. */
659 g
->state
.stroke
= style
;
661 /* Forward to native. */
662 if (g
->impl
->setStrokeStyle
!= NULL
)
663 return g
->impl
->setStrokeStyle(g
, style
);
664 return SJME_ERROR_NONE
;
667 sjme_errorCode
sjme_scritchpen_core_translate(
668 sjme_attrInNotNull sjme_scritchui_pencil g
,
669 sjme_attrInValue sjme_jint x
,
670 sjme_attrInValue sjme_jint y
)
673 return SJME_ERROR_NULL_ARGUMENTS
;
675 /* Add to the translation. */
676 g
->state
.translate
.x
+= x
;
677 g
->state
.translate
.y
+= y
;
680 return SJME_ERROR_NONE
;