/* * Copyright (c) 2005-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * HSV (Hue/Saturation/Value) Style Color Picker Widget. * * It can be used to edit colors in any of the following representations: * - A native AG_Color(3). * - A packed pixel in a given AG_PixelFormat(3). * - RGB Components as 8- or 16-bit integers. * - RGB Components as single or double precision floats. * - HSV Components as single or double precision floats. */ #include #if defined(AG_WIDGETS) #include #include #include #include #include #include #include #ifdef AG_THREADS static AG_Mutex CopyLock = AG_MUTEX_INITIALIZER; /* Copy buffer lock */ #endif static float cH = 0.0, cS = 0.0, cV = 0.0, cA = 0.0; /* Copy buffer */ static void UpdatePixelFromHSVA(AG_HSVPal *_Nonnull); AG_HSVPal * AG_HSVPalNew(void *parent, Uint flags) { AG_HSVPal *pal; pal = Malloc(sizeof(AG_HSVPal)); AG_ObjectInit(pal, &agHSVPalClass); if (flags & AG_HSVPAL_HFILL) { WIDGET(pal)->flags |= AG_WIDGET_HFILL; } if (flags & AG_HSVPAL_VFILL) { WIDGET(pal)->flags |= AG_WIDGET_VFILL; } pal->flags |= flags; AG_ObjectAttach(parent, pal); return (pal); } /* Return the 8-bit representation of the current alpha value. */ static __inline__ Uint8 GetAlpha8(AG_HSVPal *_Nonnull pal) { AG_Variable *bAlpha; Uint8 a = 255; void *p; bAlpha = AG_GetVariable(pal, "alpha", &p); switch (AG_VARIABLE_TYPE(bAlpha)) { case AG_VARIABLE_FLOAT: a = (Uint8)((*(float *)p)*255.0f); break; case AG_VARIABLE_DOUBLE: a = (Uint8)((*(double *)p)*255.0); break; case AG_VARIABLE_INT: a = (int)((*(int *)p)); break; case AG_VARIABLE_UINT8: a = (int)((*(Uint8 *)p)); break; default: break; } AG_UnlockVariable(bAlpha); return (a); } static __inline__ void SetAlpha8(AG_HSVPal *_Nonnull pal, Uint8 a) { AG_Variable *bAlpha; void *pAlpha; bAlpha = AG_GetVariable(pal, "alpha", &pAlpha); switch (AG_VARIABLE_TYPE(bAlpha)) { case AG_VARIABLE_FLOAT: *(float *)pAlpha = (float)a/255.0f; break; case AG_VARIABLE_DOUBLE: *(double *)pAlpha = (double)a/255.0; break; case AG_VARIABLE_INT: *(int *)pAlpha = (int)a; break; case AG_VARIABLE_UINT8: *(Uint8 *)pAlpha = (Uint8)a; break; default: break; } AG_UnlockVariable(bAlpha); } static void UpdateHSVFromPixel32(AG_HSVPal *_Nonnull pal, Uint32 px) { Uint8 r, g, b, a; float h, s, v; AG_Variable *bFormat; AG_PixelFormat **pFormat; bFormat = AG_GetVariable(pal, "pixel-format", (void *)&pFormat); if (*pFormat != NULL) { AG_GetColor32_RGBA8(px, *pFormat, &r,&g,&b,&a); AG_RGB2HSV(r, g, b, &h,&s,&v); AG_SetFloat(pal, "hue", h); AG_SetFloat(pal, "saturation", s); AG_SetFloat(pal, "value", v); SetAlpha8(pal, a); } AG_UnlockVariable(bFormat); AG_Redraw(pal); } #if AG_MODEL == AG_LARGE static void UpdateHSVFromPixel64(AG_HSVPal *_Nonnull pal, Uint64 px) { Uint8 r, g, b, a; float h, s, v; AG_Variable *bFormat; AG_PixelFormat **pFormat; bFormat = AG_GetVariable(pal, "pixel-format", (void *)&pFormat); if (*pFormat != NULL) { AG_GetColor64_RGBA8(px, *pFormat, &r,&g,&b,&a); AG_RGB2HSV(r, g, b, &h,&s,&v); AG_SetFloat(pal, "hue", h); AG_SetFloat(pal, "saturation", s); AG_SetFloat(pal, "value", v); SetAlpha8(pal, a); } AG_UnlockVariable(bFormat); AG_Redraw(pal); } #endif /* AG_LARGE */ static void UpdateHSVFromColor(AG_HSVPal *_Nonnull pal) { AG_Variable *bColor; void *pColor; const AG_Color *c; float h, s, v; if ((bColor = AG_GetVariable(pal, "agcolor", &pColor)) == NULL) { return; } c = (const AG_Color *)pColor; AG_RGB2HSV(c->r, c->g, c->b, &h,&s,&v); AG_SetFloat(pal, "hue", h); AG_SetFloat(pal, "saturation", s); AG_SetFloat(pal, "value", v); if ((pal->flags & AG_HSVPAL_NOALPHA) == 0) { SetAlpha8(pal, c->a); } AG_UnlockVariable(bColor); pal->flags |= AG_HSVPAL_DIRTY; AG_Redraw(pal); } static void UpdateHSVFromRGBAv(AG_HSVPal *_Nonnull pal) { AG_Variable *bRGBAv; void *RGBAv; Uint8 r, g, b, a; float h, s, v; if ((bRGBAv = AG_GetVariable(pal, "RGBAv", &RGBAv)) == NULL) { return; } switch (AG_VARIABLE_TYPE(bRGBAv)) { case AG_VARIABLE_FLOAT: r = (Uint8)(((float *)RGBAv)[0] * 255.0f); g = (Uint8)(((float *)RGBAv)[1] * 255.0f); b = (Uint8)(((float *)RGBAv)[2] * 255.0f); a = (Uint8)(((float *)RGBAv)[3] * 255.0f); break; case AG_VARIABLE_DOUBLE: r = (Uint8)(((double *)RGBAv)[0] * 255.0); g = (Uint8)(((double *)RGBAv)[1] * 255.0); b = (Uint8)(((double *)RGBAv)[2] * 255.0); a = (Uint8)(((double *)RGBAv)[3] * 255.0); break; case AG_VARIABLE_INT: r = (Uint8)(((int *)RGBAv)[0]); g = (Uint8)(((int *)RGBAv)[1]); b = (Uint8)(((int *)RGBAv)[2]); a = (Uint8)(((int *)RGBAv)[3]); break; case AG_VARIABLE_UINT8: r = ((Uint8 *)RGBAv)[0]; g = ((Uint8 *)RGBAv)[1]; b = ((Uint8 *)RGBAv)[2]; a = ((Uint8 *)RGBAv)[3]; break; default: r = 0; g = 0; b = 0; a = 0; break; } AG_RGB2HSV(r,g,b, &h,&s,&v); AG_SetFloat(pal, "hue", h); AG_SetFloat(pal, "saturation", s); AG_SetFloat(pal, "value", v); SetAlpha8(pal, a); AG_UnlockVariable(bRGBAv); pal->flags |= AG_HSVPAL_DIRTY; AG_Redraw(pal); } static void UpdateHSVFromRGBv(AG_HSVPal *_Nonnull pal) { AG_Variable *bRGBv; void *RGBv; Uint8 r, g, b; float h, s, v; if ((bRGBv = AG_GetVariable(pal, "RGBv", &RGBv)) == NULL) { return; } switch (AG_VARIABLE_TYPE(bRGBv)) { case AG_VARIABLE_FLOAT: r = (Uint8)(((float *)RGBv)[0] * 255.0f); g = (Uint8)(((float *)RGBv)[1] * 255.0f); b = (Uint8)(((float *)RGBv)[2] * 255.0f); break; case AG_VARIABLE_DOUBLE: r = (Uint8)(((double *)RGBv)[0] * 255.0); g = (Uint8)(((double *)RGBv)[1] * 255.0); b = (Uint8)(((double *)RGBv)[2] * 255.0); break; case AG_VARIABLE_INT: r = (Uint8)(((int *)RGBv)[0]); g = (Uint8)(((int *)RGBv)[1]); b = (Uint8)(((int *)RGBv)[2]); break; case AG_VARIABLE_UINT8: r = ((Uint8 *)RGBv)[0]; g = ((Uint8 *)RGBv)[1]; b = ((Uint8 *)RGBv)[2]; break; default: r = 0; g = 0; b = 0; break; } AG_RGB2HSV(r,g,b, &h,&s,&v); AG_SetFloat(pal, "hue", h); AG_SetFloat(pal, "saturation", s); AG_SetFloat(pal, "value", v); AG_UnlockVariable(bRGBv); pal->flags |= AG_HSVPAL_DIRTY; AG_Redraw(pal); } void AG_HSVPal_UpdateHue(AG_HSVPal *_Nonnull pal, int x, int y) { float h; AG_OBJECT_ISA(pal, "AG_Widget:AG_HSVPal:*"); h = Atan2((float)y, (float)x); if (h < 0) { h += 2.0f*(float)AG_PI; } AG_SetFloat(pal, "hue", h/(2.0f*AG_PI)*360.0f); UpdatePixelFromHSVA(pal); AG_PostEvent(pal, "h-changed", NULL); pal->flags |= AG_HSVPAL_DIRTY; } void AG_HSVPal_UpdateSV(AG_HSVPal *_Nonnull pal, int ax, int ay) { float s,v, hTri; int x = ax - pal->triangle.x; int y = ay - pal->triangle.y; int y_2; AG_OBJECT_ISA(pal, "AG_Widget:AG_HSVPal:*"); if (y >= pal->triangle.h) { y = pal->triangle.h-1; } y_2 = y >> 1; if (x < -y_2) { x = -y_2; } if (x > y_2) { x = y_2; } hTri = (float)pal->triangle.h; s = 1.0f - (float)(y) / hTri; v = 1.0f - (float)(x + y_2) / hTri; if (s < AG_SATURATION_EPSILON) { s = 0.00001f; } else if (s > 1.0f) { s = 1.0f; } if (v < AG_VALUE_EPSILON) { v = 0.0001f; } else if (v > 1.0f) { v = 1.0f; } AG_SetFloat(pal, "saturation", s); AG_SetFloat(pal, "value", v); UpdatePixelFromHSVA(pal); AG_PostEvent(pal, "sv-changed", NULL); pal->flags |= AG_HSVPAL_DIRTY; } static void UpdatePixelFromHSVA(AG_HSVPal *_Nonnull pal) { Uint8 r,g,b,a; AG_Variable *bColor, *bv; AG_Color *pColor; void *v; AG_MapHSVf_RGB8(AG_GetFloat(pal, "hue"), AG_GetFloat(pal, "saturation"), AG_GetFloat(pal, "value"), &r, &g, &b); a = GetAlpha8(pal); if ((bv = AG_AccessVariable(pal, "RGBv")) != NULL) { v = bv->data.p; switch (AG_VARIABLE_TYPE(bv)) { case AG_VARIABLE_FLOAT: ((float *)v)[0] = (float)r/255.0f; ((float *)v)[1] = (float)g/255.0f; ((float *)v)[2] = (float)b/255.0f; break; case AG_VARIABLE_DOUBLE: ((double *)v)[0] = (double)r/255.0; ((double *)v)[1] = (double)g/255.0; ((double *)v)[2] = (double)b/255.0; break; case AG_VARIABLE_INT: ((int *)v)[0] = (int)r; ((int *)v)[1] = (int)g; ((int *)v)[2] = (int)b; break; case AG_VARIABLE_UINT8: ((Uint8 *)v)[0] = r; ((Uint8 *)v)[1] = g; ((Uint8 *)v)[2] = b; break; default: break; } AG_UnlockVariable(bv); } if ((bv = AG_AccessVariable(pal, "RGBAv")) != NULL) { v = bv->data.p; switch (AG_VARIABLE_TYPE(bv)) { case AG_VARIABLE_FLOAT: ((float *)v)[0] = (float)r/255.0; ((float *)v)[1] = (float)g/255.0; ((float *)v)[2] = (float)b/255.0; ((float *)v)[3] = (float)a/255.0; break; case AG_VARIABLE_DOUBLE: ((double *)v)[0] = (double)r/255.0; ((double *)v)[1] = (double)g/255.0; ((double *)v)[2] = (double)b/255.0; ((double *)v)[3] = (double)a/255.0; break; case AG_VARIABLE_INT: ((int *)v)[0] = (int)r; ((int *)v)[1] = (int)g; ((int *)v)[2] = (int)b; ((int *)v)[3] = (int)a; break; case AG_VARIABLE_UINT8: ((Uint8 *)v)[0] = r; ((Uint8 *)v)[1] = g; ((Uint8 *)v)[2] = b; ((Uint8 *)v)[3] = a; break; default: break; } AG_UnlockVariable(bv); } if (pal->flags & AG_HSVPAL_PIXEL) { AG_PixelFormat **pFormat; AG_Variable *bFormat; if ((bFormat = AG_GetVariable(pal, "pixel-format", (void *)&pFormat))) { AG_SetUint32(pal, "pixel", AG_MapPixel32_RGBA8(*pFormat, r,g,b,a)); #if AG_MODEL == AG_LARGE AG_SetUint64(pal, "pixel64", AG_MapPixel64_RGBA8(*pFormat, r,g,b,a)); #endif } AG_UnlockVariable(bFormat); } bColor = AG_GetVariable(pal, "agcolor", (void *)&pColor); pColor->r = AG_8toH(r); pColor->g = AG_8toH(g); pColor->b = AG_8toH(b); pColor->a = AG_8toH(a); AG_UnlockVariable(bColor); AG_Redraw(pal); } static void UpdateAlpha(AG_HSVPal *_Nonnull pal, int x) { AG_Variable *bAlpha; void *pAlpha; x -= WIDGET(pal)->paddingLeft; bAlpha = AG_GetVariable(pal, "alpha", &pAlpha); switch (AG_VARIABLE_TYPE(bAlpha)) { case AG_VARIABLE_FLOAT: *(float *)pAlpha = ((float)x)/((float)pal->rPrev.w); if (*(float *)pAlpha > 1.0f) { *(float *)pAlpha = 1.0f; } else if (*(float *)pAlpha < 0.0f) { *(float *)pAlpha = 0.0f; } break; case AG_VARIABLE_DOUBLE: *(double *)pAlpha = ((double)x)/((double)pal->rPrev.w); if (*(double *)pAlpha > 1.0) { *(double *)pAlpha = 1.0; } else if (*(double *)pAlpha < 0.0) { *(double *)pAlpha = 0.0; } break; case AG_VARIABLE_INT: *(int *)pAlpha = x/pal->rPrev.w; if (*(int *)pAlpha > 255) { *(int *)pAlpha = 255; } else if (*(int *)pAlpha < 0) { *(int *)pAlpha = 0; } break; case AG_VARIABLE_UINT8: *(Uint8 *)pAlpha = (Uint8)(x/pal->rPrev.w); break; default: break; } AG_UnlockVariable(bAlpha); UpdatePixelFromHSVA(pal); AG_PostEvent(pal, "a-changed", NULL); } static void CloseMenu(AG_HSVPal *_Nonnull pal) { AG_MenuCollapse(pal->menu_item); pal->menu = NULL; pal->menu_item = NULL; pal->menu_win = NULL; } #if 0 static void EditNumValues(AG_Event *event) { AG_HSVPal *pal = AG_HSVPAL_PTR(1); AG_Window *pwin; AG_Window *win; AG_Numerical *num; AG_Variable *b1, *b2; float v; if ((pwin = AG_ParentWindow(pal)) == NULL) return; if ((win = AG_WindowNewNamed(AG_WINDOW_NOMAXIMIZE, "hsvpal-%p-numedit", pal)) == NULL) { return; } AG_WindowSetCaption(win, _("Color values")); AG_WindowSetPosition(win, AG_WINDOW_LOWER_LEFT, 0); AG_WindowSetCloseAction(win, AG_WINDOW_DETACH); { AG_Variable *bAlpha; void *pAlpha; num = AG_NumericalNew(win, 0, NULL, _("Hue: ")); AG_NumericalSizeHint(num, "000"); AG_WidgetCopyBinding(num, "value", pal, "hue"); AG_NumericalSetRange(num, 0.0, 359.0); AG_NumericalSetIncrement(num, 1); AG_NumericalSetPrecision(num, "f", 0); num = AG_NumericalNew(win, 0, NULL, _("Saturation: ")); AG_NumericalSizeHint(num, "00.00"); AG_WidgetCopyBinding(num, "value", pal, "saturation"); AG_NumericalSetRange(num, 0.0, 1.0); AG_NumericalSetIncrement(num, 0.01); AG_NumericalSetPrecision(num, "f", 2); num = AG_NumericalNew(win, 0, NULL, _("Value: ")); AG_NumericalSizeHint(num, "00.00"); AG_WidgetCopyBinding(num, "value", pal, "value"); AG_NumericalSetRange(num, 0.0, 1.0); AG_NumericalSetIncrement(num, 0.01); AG_NumericalSetPrecision(num, "f", 2); num = AG_NumericalNew(win, 0, NULL, _("Alpha: ")); AG_NumericalSizeHint(num, "0.000"); AG_WidgetCopyBinding(num, "value", pal, "alpha"); bAlpha = AG_GetVariable(pal, "alpha", &pAlpha); switch (AG_VARIABLE_TYPE(bAlpha)) { case AG_VARIABLE_FLOAT: case AG_VARIABLE_DOUBLE: AG_NumericalSetRange(num, 0.0, 1.0); AG_NumericalSetIncrement(num, 0.005); AG_NumericalSetPrecision(num, "f", 3); break; case AG_VARIABLE_INT: case AG_VARIABLE_UINT: case AG_VARIABLE_UINT8: AG_NumericalSetRange(num, 0.0, 255.0); AG_NumericalSetIncrement(num, 1.0); AG_NumericalSetPrecision(num, "f", 0); break; default: break; } AG_UnlockVariable(bAlpha); } AG_WindowAttach(pwin, win); AG_WindowShow(win); } #endif static void SetComplementaryColor(AG_Event *_Nonnull event) { AG_HSVPal *pal = AG_HSVPAL_PTR(1); float hue; AG_ObjectLock(pal); hue = AG_GetFloat(pal, "hue"); AG_SetFloat(pal, "hue", ((int)hue+180) % 359); UpdatePixelFromHSVA(pal); pal->flags |= AG_HSVPAL_DIRTY; AG_PostEvent(pal, "h-changed", NULL); AG_PostEvent(pal, "sv-changed", NULL); AG_ObjectUnlock(pal); } static void CopyColor(AG_Event *_Nonnull event) { AG_HSVPal *pal = AG_HSVPAL_PTR(1); AG_ObjectLock(pal); AG_MutexLock(&CopyLock); cH = AG_GetFloat(pal, "hue"); cS = AG_GetFloat(pal, "saturation"); cV = AG_GetFloat(pal, "value"); cA = AG_GetFloat(pal, "alpha"); AG_MutexUnlock(&CopyLock); AG_ObjectUnlock(pal); AG_Redraw(pal); } static void PasteColor(AG_Event *_Nonnull event) { AG_HSVPal *pal = AG_HSVPAL_PTR(1); AG_ObjectLock(pal); AG_MutexLock(&CopyLock); AG_SetFloat(pal, "hue", cH); AG_SetFloat(pal, "saturation", cS); AG_SetFloat(pal, "value", cV); AG_SetFloat(pal, "alpha", cA); AG_MutexUnlock(&CopyLock); UpdatePixelFromHSVA(pal); pal->flags |= AG_HSVPAL_DIRTY; AG_PostEvent(pal, "h-changed", NULL); AG_PostEvent(pal, "sv-changed", NULL); AG_ObjectUnlock(pal); } static void OpenMenu(AG_HSVPal *_Nonnull pal, int x, int y) { if (pal->menu != NULL) CloseMenu(pal); pal->menu = AG_MenuNew(NULL, 0); pal->menu_item = AG_MenuNode(pal->menu->root, NULL, NULL); { #if 0 AG_MenuAction(pal->menu_item, _("Edit numerically"), NULL, EditNumValues, "%p", pal); #endif AG_MenuAction(pal->menu_item, _("Copy color"), agIconSave.s, CopyColor, "%p", pal); AG_MenuAction(pal->menu_item, _("Paste color"), agIconLoad.s, PasteColor, "%p", pal); AG_MenuAction(pal->menu_item, _("Complementary color"), NULL, SetComplementaryColor, "%p", pal); AG_MenuSeparator(pal->menu_item); AG_MenuFlags(pal->menu_item, _("Show RGB value"), agIconMagnifier.s, &pal->flags, AG_HSVPAL_SHOW_RGB, 0); AG_MenuFlags(pal->menu_item, _("Show HSV value"), agIconMagnifier.s, &pal->flags, AG_HSVPAL_SHOW_HSV, 0); } pal->menu->itemSel = pal->menu_item; pal->menu_win = AG_MenuExpand(pal, pal->menu_item, x, y); } static void MouseButtonDown(void *obj, AG_MouseButton button, int x, int y) { AG_HSVPal *pal = obj; if ((WIDGET(pal)->flags & AG_WIDGET_FOCUSABLE) && !AG_WidgetIsFocused(pal)) AG_WidgetFocus(pal); switch (button) { case AG_MOUSE_LEFT: if ((pal->flags & AG_HSVPAL_NOALPHA) == 0 && y > pal->rPrev.y) { UpdateAlpha(pal, x); pal->state = AG_HSVPAL_SEL_A; } else { const float r = Hypot((float)(x - pal->circle.x), (float)(y - pal->circle.y)); if (r > (float)pal->circle.rIn) { AG_HSVPal_UpdateHue(pal, x - pal->circle.x, y - pal->circle.y); pal->state = AG_HSVPAL_SEL_H; } else { AG_HSVPal_UpdateSV(pal, x,y); pal->state = AG_HSVPAL_SEL_SV; } } break; case AG_MOUSE_MIDDLE: case AG_MOUSE_RIGHT: OpenMenu(pal, x,y); break; default: break; } } static void MouseButtonUp(void *obj, AG_MouseButton button, int x, int y) { AG_HSVPal *pal = obj; pal->state = AG_HSVPAL_SEL_NONE; AG_Redraw(pal); } static void MouseMotion(void *obj, int x, int y, int dx, int dy) { AG_HSVPal *pal = obj; switch (pal->state) { case AG_HSVPAL_SEL_NONE: break; case AG_HSVPAL_SEL_H: AG_HSVPal_UpdateHue(pal, x - pal->circle.x, y - pal->circle.y); break; case AG_HSVPAL_SEL_SV: AG_HSVPal_UpdateSV(pal, x, y); break; case AG_HSVPAL_SEL_A: UpdateAlpha(pal, x); break; default: break; } } static Uint32 KeyMoveTimeout(AG_Timer *_Nonnull to, AG_Event *_Nonnull event) { AG_HSVPal *pal = AG_HSVPAL_SELF(); const int keysym = AG_INT(1); const int keymod = AG_INT(2); const int doHue = (keymod & AG_KEYMOD_SHIFT) || (keymod & AG_KEYMOD_CTRL) || (keymod & AG_KEYMOD_ALT); Uint32 rv = 10; float sat, x; switch (keysym) { case AG_KEY_UP: if (doHue) { x = AG_GetFloat(pal,"hue"); if ((x -= 2.0f) < 0.0f) { x = 360.0f; } AG_SetFloat(pal, "hue", x); goto hue_changed; } else { sat = AG_GetFloat(pal,"saturation"); if ((sat += 0.015f) > 1.0f) { sat = 1.0f; rv = 0; } AG_SetFloat(pal, "saturation", sat); goto sv_changed; } break; case AG_KEY_DOWN: if (doHue) { x = AG_GetFloat(pal,"hue"); if ((x += 2.0f) > 360.0f) { x = 0.0f; } AG_SetFloat(pal, "hue", x); goto hue_changed; } else { sat = AG_GetFloat(pal,"saturation"); if ((sat -= 0.015f) < 0.0f) { sat = 0.0f; rv = 0; } AG_SetFloat(pal, "saturation", sat); goto sv_changed; } break; case AG_KEY_LEFT: if (doHue) { x = AG_GetFloat(pal,"hue"); if ((x -= 2.0f) < 0.0f) { x = 360.0f; } AG_SetFloat(pal, "hue", x); goto hue_changed; } else { x = AG_GetFloat(pal,"value"); if ((x += 0.01f) > 1.0f) { x = 1.0f; rv = 0; } AG_SetFloat(pal, "value", x); goto sv_changed; } break; case AG_KEY_RIGHT: if (doHue) { x = AG_GetFloat(pal,"hue"); if ((x += 2.0f) > 360.0f) { x = 0.0f; } AG_SetFloat(pal, "hue", x); goto hue_changed; } else { x = AG_GetFloat(pal,"value"); if ((x -= 0.01f) < 0.0f) { x = 0.0f; rv = 0; } AG_SetFloat(pal, "value", x); goto sv_changed; } break; } return (rv); hue_changed: UpdatePixelFromHSVA(pal); pal->flags |= AG_HSVPAL_DIRTY; AG_PostEvent(pal, "h-changed", NULL); return (rv); sv_changed: UpdatePixelFromHSVA(pal); pal->flags |= AG_HSVPAL_DIRTY; AG_PostEvent(pal, "sv-changed", NULL); return (rv); } static void KeyDown(void *obj, AG_KeySym ks, AG_KeyMod kmod, AG_Char ch) { AG_HSVPal *pal = obj; AG_Timer *toMove; switch (ks) { case AG_KEY_UP: case AG_KEY_DOWN: case AG_KEY_RIGHT: case AG_KEY_LEFT: toMove = &pal->toMove[ks - AG_KEY_UP]; AG_AddTimer(pal, toMove, 250, KeyMoveTimeout,"%i,%i",ks,kmod); AG_ExecTimer(toMove); break; default: break; } } static void KeyUp(void *obj, AG_KeySym ks, AG_KeyMod kmod, AG_Char ch) { AG_HSVPal *pal = obj; switch (ks) { case AG_KEY_UP: case AG_KEY_DOWN: case AG_KEY_LEFT: case AG_KEY_RIGHT: AG_DelTimer(pal, &pal->toMove[ks - AG_KEY_UP]); break; default: break; } } static void Bound(AG_Event *_Nonnull event) { AG_HSVPal *pal = AG_HSVPAL_SELF(); const AG_Variable *V = AG_PTR(1); if (AG_VARIABLE_TYPE(V) == AG_VARIABLE_UINT32 && strcmp(V->name, "pixel") == 0) { pal->flags |= AG_HSVPAL_PIXEL; UpdateHSVFromPixel32(pal, *(Uint32 *)V->data.p); #if AG_MODEL == AG_LARGE } else if (AG_VARIABLE_TYPE(V) == AG_VARIABLE_UINT64 && strcmp(V->name, "pixel64") == 0) { pal->flags |= AG_HSVPAL_PIXEL; UpdateHSVFromPixel64(pal, *(Uint64 *)V->data.p); #endif } else if (strcmp(V->name, "agcolor") == 0) { UpdateHSVFromColor(pal); } else if (strcmp(V->name, "RGBAv") == 0) { UpdateHSVFromRGBAv(pal); } else if (strcmp(V->name, "RGBv") == 0) { UpdateHSVFromRGBv(pal); } else if (strcmp(V->name, "pixel-format") == 0 && !(pal->flags & AG_HSVPAL_FORCE_NOALPHA)) { AG_PixelFormat **pFormat = (AG_PixelFormat **)V->data.p; if ((*pFormat)->Amask != 0) { pal->flags &= ~(AG_HSVPAL_NOALPHA); } else { pal->flags |= AG_HSVPAL_NOALPHA; } } AG_Redraw(pal); } static void OnAttach(AG_Event *_Nonnull event) { AG_HSVPal *pal = AG_HSVPAL_SELF(); pal->pixel = AG_MapPixel32_RGBA8(agSurfaceFmt, 0,0,0,255); AG_ColorRGBA_8(&pal->color, 0,0,0, 255); AG_BindPointer(pal, "pixel-format", (void *)&agSurfaceFmt); } static void Init(void *_Nonnull obj) { AG_HSVPal *pal = obj; WIDGET(pal)->flags |= AG_WIDGET_FOCUSABLE | AG_WIDGET_USE_TEXT; pal->flags = 0; pal->h = 0.0f; pal->s = 0.0f; pal->v = 0.0f; pal->a = 1.0f; pal->pixel = 0; AG_ColorBlack(&pal->color); pal->circle.spacing = 10; pal->state = AG_HSVPAL_SEL_NONE; pal->surface = NULL; pal->surfaceId = -1; pal->menu = NULL; pal->menu_item = NULL; pal->menu_win = NULL; AG_ColorRGB_8(&pal->cTile[0], 140,140,140); AG_ColorRGB_8(&pal->cTile[1], 70,70,70); AG_InitTimer(&pal->toMove[0], "moveUp", 0); AG_InitTimer(&pal->toMove[1], "moveDown", 0); AG_InitTimer(&pal->toMove[2], "moveLeft", 0); AG_InitTimer(&pal->toMove[3], "moveRight", 0); AG_BindFloat(pal, "hue", &pal->h); AG_BindFloat(pal, "saturation", &pal->s); AG_BindFloat(pal, "value", &pal->v); AG_BindFloat(pal, "alpha", &pal->a); AG_BindUint32(pal, "pixel", &pal->pixel); #if AG_MODEL == AG_LARGE AG_BindUint64(pal, "pixel64", &pal->pixel64); #endif AG_BindPointer(pal, "agcolor", (void *)&pal->color); AG_BindPointer(pal, "pixel-format", NULL); AG_SetEvent(pal, "bound", Bound, NULL); OBJECT(pal)->flags |= AG_OBJECT_BOUND_EVENTS; AG_AddEvent(pal, "attached", OnAttach, NULL); } /* Render static components (for a given Hue) to a surface. */ static void RenderStatic(AG_HSVPal *_Nonnull pal) { AG_Surface *S = pal->surface; float hue,sat, val, h; AG_Color c; AG_Pixel px; int x, y, i; AG_FillRect(S, NULL, &WCOLOR(pal,BG_COLOR)); hue = AG_GetFloat(pal, "hue"); sat = AG_GetFloat(pal, "saturation"); val = AG_GetFloat(pal, "value"); /* Render the circle of hues. */ for (h = 0.0f; h < 2.0f*AG_PI; h += pal->circle.dh) { AG_MapHSVf_RGB((h / (2.0f*AG_PI)*360.0f), 1.0f, 1.0f, &c.r, &c.g, &c.b); c.a = AG_OPAQUE; px = AG_MapPixel(&S->format, &c); for (i = 0; i < pal->circle.width; i++) { x = (pal->circle.rOut - i)*Cos(h); y = (pal->circle.rOut - i)*Sin(h); AG_SurfacePut(S, pal->circle.x + x, pal->circle.y + y, px); } } /* Render the triangle of saturation and value. */ for (y = 0; y < pal->triangle.h; y += 2) { float sat = (float)(pal->triangle.h - y) / (float)(pal->triangle.h); for (x = 0; x < y; x++) { AG_MapHSVf_RGB(hue, sat, 1.0f - ((float)x/(float)pal->triangle.h), &c.r, &c.g, &c.b); px = AG_MapPixel(&S->format, &c); AG_SurfacePut(S, pal->triangle.x + x - (y >> 1), pal->triangle.y + y, px); AG_SurfacePut(S, pal->triangle.x + x - (y >> 1), pal->triangle.y + y + 1, px); } } if (!(pal->flags & AG_HSVPAL_NOALPHA)) { #if 0 AG_Rect rd; for (y = 16; y < pal->rPrev.h+16; y+=8) { for (x = 0; x < pal->rPrev.w; x+=16) { rd.w = 8; rd.h = 8; rd.x = pal->rPrev.x+x; rd.y = pal->rPrev.y+y; AG_FillRect(S, &rd, &pal->cTile[0]); } y += 8; for (x = 8; x < pal->rPrev.w; x+=16) { rd.w = 8; rd.h = 8; rd.x = pal->rPrev.x+x; rd.y = pal->rPrev.y+y; AG_FillRect(S, &rd, &pal->cTile[1]); } } #endif AG_MapHSVf_RGB(hue, sat, val, &c.r, &c.g, &c.b); for (y = pal->rPrev.y; y < pal->rPrev.y + pal->rPrev.h; y++) { for (x = pal->rPrev.x, i=0, c.a=0; x < pal->rPrev.x + pal->rPrev.w; x++, i++) { AG_SurfaceBlend(S, x,y, &c); c.a = i*AG_OPAQUE/pal->rPrev.w; } } } AG_WidgetUpdateSurface(pal, pal->surfaceId); } static void SizeRequest(void *_Nonnull obj, AG_SizeReq *_Nonnull r) { AG_HSVPal *pal = obj; r->w = WIDGET(pal)->paddingLeft + 128 + WIDGET(pal)->paddingRight; r->h = WIDGET(pal)->paddingTop + 128 + WIDGET(pal)->paddingBottom; } static int SizeAllocate(void *_Nonnull obj, const AG_SizeAlloc *_Nonnull a) { AG_HSVPal *pal = obj; const int paddingLeft = WIDGET(pal)->paddingLeft; const int paddingRight = WIDGET(pal)->paddingRight; const int paddingTop = WIDGET(pal)->paddingTop; const int paddingBottom = WIDGET(pal)->paddingBottom; const int wAvail = a->w - paddingLeft - paddingRight; int hPreview = MAX(0, 32 - paddingBottom); if (a->w < 32 || a->h < 32) { return (-1); } else if (a->w < 50 || a->h < 50) { pal->circle.width = 4; pal->selcircle_r = 3; hPreview = 16; } else if (a->w < 100 || a->h < 100) { pal->circle.width = 6; pal->selcircle_r = 2; } else if (a->w < 200 || a->h < 200) { pal->circle.width = 12; pal->selcircle_r = 2; } else if (a->w < 300 || a->h < 300) { pal->circle.width = 20; pal->selcircle_r = 4; } else { pal->circle.width = 30; pal->selcircle_r = 6; } pal->rPrev.x = paddingLeft; pal->rPrev.y = a->h - hPreview - paddingBottom; if ((pal->flags & AG_HSVPAL_NOPREVIEW) == 0) { pal->rPrev.w = wAvail; pal->rPrev.h = hPreview; } else { pal->rPrev.w = 0; pal->rPrev.h = 0; } pal->circle.rOut = MIN(wAvail, a->h - paddingTop - paddingBottom); pal->circle.rOut = MIN(pal->circle.rOut, (a->h - pal->rPrev.h - paddingTop - paddingBottom)); pal->circle.rOut >>= 1; if (pal->circle.rOut < 16) return (-1); pal->circle.rIn = pal->circle.rOut - pal->circle.width; pal->circle.dh = (float)(1.0/(pal->circle.rOut*AG_PI)); pal->circle.x = paddingLeft + (wAvail >> 1); pal->circle.y = (a->h - pal->rPrev.h) >> 1; pal->triangle.x = pal->circle.x; pal->triangle.y = pal->circle.y + pal->circle.width - pal->circle.rOut; pal->triangle.h = pal->circle.rIn*Sin((37.0f / 360.0f)*(2.0f * AG_PI)) -pal->circle.rIn*Sin((270.0f / 360.0f)*(2.0f * AG_PI)); pal->flags |= AG_HSVPAL_DIRTY; return (0); } static void Draw(void *_Nonnull obj) { AG_HSVPal *pal = obj; AG_Color c, cInv, cBlack; float hueDeg, hueRad, sat, val; Uint8 r, g, b, a; const int w = WIDTH(pal); const int h = HEIGHT(pal); int x, y, rad; int xSel, ySel; if (pal->surface == NULL) { pal->surface = AG_SurfaceStdRGBA(w,h); pal->surfaceId = AG_WidgetMapSurface(pal, pal->surface); RenderStatic(pal); } else if (pal->surface->w != w || /* Resized */ pal->surface->h != h) { pal->surface = AG_SurfaceStdRGBA(w,h); RenderStatic(pal); AG_WidgetReplaceSurface(pal, pal->surfaceId, pal->surface); } else if (pal->flags & AG_HSVPAL_DIRTY) { RenderStatic(pal); AG_WidgetUpdateSurface(pal, pal->surfaceId); } pal->flags &= ~(AG_HSVPAL_DIRTY); AG_WidgetBlitFrom(pal, pal->surfaceId, NULL, 0,0); hueDeg = AG_GetFloat(pal, "hue"); hueRad = (hueDeg / 360.0f) * 2.0f*AG_PI; sat = AG_GetFloat(pal, "saturation"); val = AG_GetFloat(pal, "value"); a = (Uint8)(AG_GetFloat(pal, "alpha")*255.0f); AG_MapHSVf_RGB8(hueDeg, sat, val, &r,&g,&b); c.r = AG_8toH(r); c.g = AG_8toH(g); c.b = AG_8toH(b); c.a = AG_OPAQUE; cInv.r = AG_OPAQUE - c.r; cInv.g = AG_OPAQUE - c.g; cInv.b = AG_OPAQUE - c.b; cInv.a = AG_OPAQUE; AG_ColorBlack(&cBlack); /* Hue */ rad = pal->circle.rIn + (pal->circle.width >> 1); xSel = pal->circle.x + rad*Cos(hueRad); ySel = pal->circle.y + rad*Sin(hueRad); AG_DrawCircleFilled(pal, xSel, ySel, pal->selcircle_r + ((pal->state == AG_HSVPAL_SEL_H) ? 2 : 0), &c); AG_DrawCircle(pal, xSel, ySel, pal->selcircle_r + 1 + ((pal->state == AG_HSVPAL_SEL_H) ? 2 : 0), &cInv); AG_DrawCircle(pal, xSel, ySel, pal->selcircle_r + 2 + ((pal->state == AG_HSVPAL_SEL_H) ? 2 : 0), &cBlack); /* Saturation and Value */ /* The rendering routine uses (v = 1 - x/h), so (x = -v*h + h). */ y = (int)((1.0 - sat) * (float)pal->triangle.h); x = (int)(-(val*(float)pal->triangle.h - (float)pal->triangle.h)); if (x < 0) { x = 0; } if (x > y) { x = y; } xSel = pal->triangle.x + x - (y >> 1); ySel = pal->triangle.y + y; AG_DrawCircle(pal, xSel, ySel, pal->selcircle_r + ((pal->state == AG_HSVPAL_SEL_SV) ? 2 : 0), &c); AG_DrawCircle(pal, xSel, ySel, pal->selcircle_r + 1 + ((pal->state == AG_HSVPAL_SEL_SV) ? 2 : 0), &cInv); AG_DrawCircle(pal, xSel, ySel, pal->selcircle_r + 2 + ((pal->state == AG_HSVPAL_SEL_SV) ? 2 : 0), &cBlack); if (!(pal->flags & AG_HSVPAL_NOALPHA)) { /* Transparency preview */ int lx = 1 + pal->rPrev.x + ((a*pal->rPrev.w) >> 8); int ly1 = pal->rPrev.y; int ly2 = pal->rPrev.y + pal->rPrev.h; if (lx > w-2) { lx = w-2; } AG_DrawLineV(pal, lx, ly1, ly2, &WCOLOR(pal, LINE_COLOR)); AG_DrawLineV(pal, lx+2, ly1, ly2, &WCOLOR(pal, LINE_COLOR)); AG_DrawRectOutline(pal, &pal->rPrev, &WCOLOR(pal,LINE_COLOR)); } else { if (!(pal->flags & AG_HSVPAL_NOPREVIEW)) AG_DrawRectFilled(pal, &pal->rPrev, &c); } /* Display RGB/HSV values */ if (pal->flags & AG_HSVPAL_SHOW_RGB_HSV) { AG_Rect rClip; AG_Surface *S; /* XXX TODO cache rendered text */ AG_TextBGColor(&c); if (sat > 0.66f) { if (val < 0.66f || ((hueDeg > 25.0f && hueDeg < 200.0f))) { AG_TextColor(&cBlack); } else { AG_TextColorRGB(255,255,255); } } else { if (val < 0.66f) { AG_TextColorRGB(255,255,255); } else { AG_TextColor(&cBlack); } } if ((pal->flags & AG_HSVPAL_SHOW_RGB) && (pal->flags & AG_HSVPAL_SHOW_HSV)) { S = AG_TextRenderF( " %u %u %u \n" " %.1f %.2f %.2f ", r,g,b, hueDeg, sat, val); } else if (pal->flags & AG_HSVPAL_SHOW_RGB) { S = AG_TextRenderF(" %u %u %u ", r, g, b); } else { S = AG_TextRenderF(" %.1f %.2f %.2f ", hueDeg, sat, val); } if (S->w > w-2) { rClip.x = 0; rClip.y = 0; rClip.w = w-2; rClip.h = h-2; AG_PushClipRect(pal, &rClip); } AG_WidgetBlit(pal, S, (w >> 1) - (S->w >> 1), pal->rPrev.y + (pal->rPrev.h >> 1) - (S->h >> 1)); if (S->w > w-2) AG_PopClipRect(pal); AG_SurfaceFree(S); } if (AG_WidgetIsFocused(pal)) AG_DrawRectOutline(pal, &WIDGET(pal)->r, &WCOLOR_FOCUSED(pal,LINE_COLOR)); } AG_WidgetClass agHSVPalClass = { { "Agar(Widget:HSVPal)", sizeof(AG_HSVPal), { 1,0, AGC_HSVPAL, 0x1F3A8 }, Init, NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate, MouseButtonDown, MouseButtonUp, MouseMotion, KeyDown, KeyUp, NULL, /* touch */ NULL, /* ctrl */ NULL /* joy */ }; #endif /* AG_WIDGETS */