/* * This file is part of Espruino, a JavaScript interpreter for Microcontrollers * * Copyright (C) 2013 Gordon Williams * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * ---------------------------------------------------------------------------- * This file is designed to be parsed during the build process * * Contains JavaScript Graphics Draw Functions * ---------------------------------------------------------------------------- */ #include "jswrap_graphics.h" #include "jsutils.h" #include "jsinteractive.h" #include "lcd_arraybuffer.h" #include "lcd_js.h" #ifdef USE_LCD_SDL #include "lcd_sdl.h" #endif #ifdef USE_LCD_FSMC #include "lcd_fsmc.h" #endif #include "bitmap_font_4x6.h" /*JSON{ "type" : "class", "class" : "Graphics" } This class provides Graphics operations that can be applied to a surface. Use Graphics.createXXX to create a graphics object that renders in the way you want. See [the Graphics page](/Graphics) for more information. **Note:** On boards that contain an LCD, there is a built-in 'LCD' object of type Graphics. For instance to draw a line you'd type: ```LCD.drawLine(0,0,100,100)``` */ /*JSON{ "type" : "idle", "generate" : "jswrap_graphics_idle" }*/ bool jswrap_graphics_idle() { graphicsIdle(); return false; } /*JSON{ "type" : "init", "generate" : "jswrap_graphics_init" }*/ void jswrap_graphics_init() { #ifdef USE_LCD_FSMC JsVar *parent = jspNewObject("LCD", "Graphics"); if (parent) { JsVar *parentObj = jsvSkipName(parent); JsGraphics gfx; graphicsStructInit(&gfx); gfx.data.type = JSGRAPHICSTYPE_FSMC; gfx.graphicsVar = parentObj; gfx.data.width = 320; gfx.data.height = 240; gfx.data.bpp = 16; lcdInit_FSMC(&gfx); lcdSetCallbacks_FSMC(&gfx); graphicsSplash(&gfx); graphicsSetVar(&gfx); jsvUnLock2(parentObj, parent); } #endif } static bool isValidBPP(int bpp) { return bpp==1 || bpp==2 || bpp==4 || bpp==8 || bpp==16 || bpp==24 || bpp==32; // currently one colour can't ever be spread across multiple bytes } /*JSON{ "type" : "staticmethod", "class" : "Graphics", "name" : "createArrayBuffer", "generate" : "jswrap_graphics_createArrayBuffer", "params" : [ ["width","int32","Pixels wide"], ["height","int32","Pixels high"], ["bpp","int32","Number of bits per pixel"], ["options","JsVar",[ "An object of other options. ```{ zigzag : true/false(default), vertical_byte : true/false(default), msb : true/false(default), color_order: 'rgb'(default),'bgr',etc }```", "zigzag = whether to alternate the direction of scanlines for rows", "vertical_byte = whether to align bits in a byte vertically or not", "msb = when bits<8, store pixels msb first", "color_order = re-orders the colour values that are supplied via setColor" ]] ], "return" : ["JsVar","The new Graphics object"], "return_object" : "Graphics" } Create a Graphics object that renders to an Array Buffer. This will have a field called 'buffer' that can get used to get at the buffer itself */ JsVar *jswrap_graphics_createArrayBuffer(int width, int height, int bpp, JsVar *options) { if (width<=0 || height<=0 || width>32767 || height>32767) { jsExceptionHere(JSET_ERROR, "Invalid Size"); return 0; } if (!isValidBPP(bpp)) { jsExceptionHere(JSET_ERROR, "Invalid BPP"); return 0; } JsVar *parent = jspNewObject(0, "Graphics"); if (!parent) return 0; // low memory JsGraphics gfx; graphicsStructInit(&gfx); gfx.data.type = JSGRAPHICSTYPE_ARRAYBUFFER; gfx.data.flags = JSGRAPHICSFLAGS_NONE; gfx.graphicsVar = parent; gfx.data.width = (unsigned short)width; gfx.data.height = (unsigned short)height; gfx.data.bpp = (unsigned char)bpp; if (jsvIsObject(options)) { if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "zigzag", 0))) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_ARRAYBUFFER_ZIGZAG); if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "msb", 0))) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_ARRAYBUFFER_MSB); if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "vertical_byte", 0))) { if (gfx.data.bpp==1) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_ARRAYBUFFER_VERTICAL_BYTE); else jsWarn("vertical_byte only works for 1bpp ArrayBuffers\n"); } JsVar *colorv = jsvObjectGetChild(options, "color_order", 0); if (colorv) { if (jsvIsStringEqual(colorv, "rgb")) ; // The default else if (!jsvIsStringEqual(colorv, "brg")) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_COLOR_BRG); else if (!jsvIsStringEqual(colorv, "bgr")) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_COLOR_BGR); else if (!jsvIsStringEqual(colorv, "gbr")) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_COLOR_GBR); else if (!jsvIsStringEqual(colorv, "grb")) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_COLOR_GRB); else if (!jsvIsStringEqual(colorv, "rbg")) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_COLOR_RBG); else jsWarn("color_order must be 3 characters"); jsvUnLock(colorv); } } lcdInit_ArrayBuffer(&gfx); graphicsSetVar(&gfx); return parent; } /*JSON{ "type" : "staticmethod", "class" : "Graphics", "name" : "createCallback", "generate" : "jswrap_graphics_createCallback", "params" : [ ["width","int32","Pixels wide"], ["height","int32","Pixels high"], ["bpp","int32","Number of bits per pixel"], ["callback","JsVar","A function of the form ```function(x,y,col)``` that is called whenever a pixel needs to be drawn, or an object with: ```{setPixel:function(x,y,col),fillRect:function(x1,y1,x2,y2,col)}```. All arguments are already bounds checked."] ], "return" : ["JsVar","The new Graphics object"], "return_object" : "Graphics" } Create a Graphics object that renders by calling a JavaScript callback function to draw pixels */ JsVar *jswrap_graphics_createCallback(int width, int height, int bpp, JsVar *callback) { if (width<=0 || height<=0 || width>32767 || height>32767) { jsExceptionHere(JSET_ERROR, "Invalid Size"); return 0; } if (!isValidBPP(bpp)) { jsExceptionHere(JSET_ERROR, "Invalid BPP"); return 0; } JsVar *callbackSetPixel = 0; JsVar *callbackFillRect = 0; if (jsvIsObject(callback)) { jsvUnLock(callbackSetPixel); callbackSetPixel = jsvObjectGetChild(callback, "setPixel", 0); callbackFillRect = jsvObjectGetChild(callback, "fillRect", 0); } else callbackSetPixel = jsvLockAgain(callback); if (!jsvIsFunction(callbackSetPixel)) { jsExceptionHere(JSET_ERROR, "Expecting Callback Function or an Object but got %t", callbackSetPixel); jsvUnLock2(callbackSetPixel, callbackFillRect); return 0; } if (!jsvIsUndefined(callbackFillRect) && !jsvIsFunction(callbackFillRect)) { jsExceptionHere(JSET_ERROR, "Expecting Callback Function or an Object but got %t", callbackFillRect); jsvUnLock2(callbackSetPixel, callbackFillRect); return 0; } JsVar *parent = jspNewObject(0, "Graphics"); if (!parent) return 0; // low memory JsGraphics gfx; graphicsStructInit(&gfx); gfx.data.type = JSGRAPHICSTYPE_JS; gfx.graphicsVar = parent; gfx.data.width = (unsigned short)width; gfx.data.height = (unsigned short)height; gfx.data.bpp = (unsigned char)bpp; lcdInit_JS(&gfx, callbackSetPixel, callbackFillRect); graphicsSetVar(&gfx); jsvUnLock2(callbackSetPixel, callbackFillRect); return parent; } #ifdef USE_LCD_SDL /*JSON{ "type" : "staticmethod", "class" : "Graphics", "name" : "createSDL", "ifdef" : "USE_LCD_SDL", "generate" : "jswrap_graphics_createSDL", "params" : [ ["width","int32","Pixels wide"], ["height","int32","Pixels high"] ], "return" : ["JsVar","The new Graphics object"], "return_object" : "Graphics" } Create a Graphics object that renders to SDL window (Linux-based devices only) */ JsVar *jswrap_graphics_createSDL(int width, int height) { if (width<=0 || height<=0 || width>32767 || height>32767) { jsExceptionHere(JSET_ERROR, "Invalid Size"); return 0; } JsVar *parent = jspNewObject(0, "Graphics"); if (!parent) return 0; // low memory JsGraphics gfx; graphicsStructInit(&gfx); gfx.data.type = JSGRAPHICSTYPE_SDL; gfx.graphicsVar = parent; gfx.data.width = (unsigned short)width; gfx.data.height = (unsigned short)height; gfx.data.bpp = 32; lcdInit_SDL(&gfx); graphicsSetVar(&gfx); return parent; } #endif /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getWidth", "generate_full" : "jswrap_graphics_getWidthOrHeight(parent, false)", "return" : ["int","The width of the LCD"] } The width of the LCD */ /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getHeight", "generate_full" : "jswrap_graphics_getWidthOrHeight(parent, true)", "return" : ["int","The height of the LCD"] } The height of the LCD */ int jswrap_graphics_getWidthOrHeight(JsVar *parent, bool height) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; if (gfx.data.flags & JSGRAPHICSFLAGS_SWAP_XY) height=!height; return height ? gfx.data.height : gfx.data.width; } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "clear", "generate" : "jswrap_graphics_clear" } Clear the LCD with the Background Color */ void jswrap_graphics_clear(JsVar *parent) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; graphicsClear(&gfx); graphicsSetVar(&gfx); // gfx data changed because modified area } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "fillRect", "generate" : "jswrap_graphics_fillRect", "params" : [ ["x1","int32","The left"], ["y1","int32","The top"], ["x2","int32","The right"], ["y2","int32","The bottom"] ] } Fill a rectangular area in the Foreground Color */ void jswrap_graphics_fillRect(JsVar *parent, int x1, int y1, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; graphicsFillRect(&gfx, (short)x1,(short)y1,(short)x2,(short)y2); graphicsSetVar(&gfx); // gfx data changed because modified area } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "drawRect", "generate" : "jswrap_graphics_drawRect", "params" : [ ["x1","int32","The left"], ["y1","int32","The top"], ["x2","int32","The right"], ["y2","int32","The bottom"] ] } Draw an unfilled rectangle 1px wide in the Foreground Color */ void jswrap_graphics_drawRect(JsVar *parent, int x1, int y1, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; graphicsDrawRect(&gfx, (short)x1,(short)y1,(short)x2,(short)y2); graphicsSetVar(&gfx); // gfx data changed because modified area } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "fillCircle", "generate" : "jswrap_graphics_fillCircle", "params" : [ ["x","int32","The X axis"], ["y","int32","The Y axis"], ["rad","int32","The circle radius"] ] } Draw a filled circle in the Foreground Color */ void jswrap_graphics_fillCircle(JsVar *parent, int x, int y, int rad) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; graphicsFillCircle(&gfx, (short)x,(short)y,(short)rad); graphicsSetVar(&gfx); // gfx data changed because modified area } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "drawCircle", "generate" : "jswrap_graphics_drawCircle", "params" : [ ["x","int32","The X axis"], ["y","int32","The Y axis"], ["rad","int32","The circle radius"] ] } Draw an unfilled circle 1px wide in the Foreground Color */ void jswrap_graphics_drawCircle(JsVar *parent, int x, int y, int rad) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; graphicsDrawCircle(&gfx, (short)x,(short)y,(short)rad); graphicsSetVar(&gfx); // gfx data changed because modified area } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getPixel", "generate" : "jswrap_graphics_getPixel", "params" : [ ["x","int32","The left"], ["y","int32","The top"] ], "return" : ["int32","The color"] } Get a pixel's color */ int jswrap_graphics_getPixel(JsVar *parent, int x, int y) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; return (int)graphicsGetPixel(&gfx, (short)x, (short)y); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "setPixel", "generate" : "jswrap_graphics_setPixel", "params" : [ ["x","int32","The left"], ["y","int32","The top"], ["col","JsVar","The color"] ] } Set a pixel's color */ void jswrap_graphics_setPixel(JsVar *parent, int x, int y, JsVar *color) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; unsigned int col = gfx.data.fgColor; if (!jsvIsUndefined(color)) col = (unsigned int)jsvGetInteger(color); graphicsSetPixel(&gfx, (short)x, (short)y, col); gfx.data.cursorX = (short)x; gfx.data.cursorY = (short)y; graphicsSetVar(&gfx); // gfx data changed because modified area } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "setColor", "generate_full" : "jswrap_graphics_setColorX(parent, r,g,b, true)", "params" : [ ["r","JsVar","Red (between 0 and 1) OR an integer representing the color in the current bit depth and color order"], ["g","JsVar","Green (between 0 and 1)"], ["b","JsVar","Blue (between 0 and 1)"] ] } Set the color to use for subsequent drawing operations */ /*JSON{ "type" : "method", "class" : "Graphics", "name" : "setBgColor", "generate_full" : "jswrap_graphics_setColorX(parent, r,g,b, false)", "params" : [ ["r","JsVar","Red (between 0 and 1) OR an integer representing the color in the current bit depth and color order"], ["g","JsVar","Green (between 0 and 1)"], ["b","JsVar","Blue (between 0 and 1)"] ] } Set the background color to use for subsequent drawing operations */ void jswrap_graphics_setColorX(JsVar *parent, JsVar *r, JsVar *g, JsVar *b, bool isForeground) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; unsigned int color = 0; JsVarFloat rf, gf, bf; rf = jsvGetFloat(r); gf = jsvGetFloat(g); bf = jsvGetFloat(b); if (!jsvIsUndefined(g) && !jsvIsUndefined(b)) { int ri = (int)(rf*256); int gi = (int)(gf*256); int bi = (int)(bf*256); if (ri>255) ri=255; if (gi>255) gi=255; if (bi>255) bi=255; if (ri<0) ri=0; if (gi<0) gi=0; if (bi<0) bi=0; // Check if we need to twiddle colors int colorMask = gfx.data.flags & JSGRAPHICSFLAGS_COLOR_MASK; if (colorMask) { int tmpr, tmpg, tmpb; tmpr = ri; tmpg = gi; tmpb = bi; switch (colorMask) { case JSGRAPHICSFLAGS_COLOR_BRG: ri = tmpb; gi = tmpr; bi = tmpg; break; case JSGRAPHICSFLAGS_COLOR_BGR: ri = tmpb; bi = tmpr; break; case JSGRAPHICSFLAGS_COLOR_GBR: ri = tmpg; gi = tmpb; bi = tmpr; break; case JSGRAPHICSFLAGS_COLOR_GRB: ri = tmpg; gi = tmpr; break; case JSGRAPHICSFLAGS_COLOR_RBG: gi = tmpb; bi = tmpg; break; default: break; } } if (gfx.data.bpp==16) { color = (unsigned int)((bi>>3) | (gi>>2)<<5 | (ri>>3)<<11); } else if (gfx.data.bpp==32) { color = 0xFF000000 | (unsigned int)(bi | (gi<<8) | (ri<<16)); } else if (gfx.data.bpp==24) { color = (unsigned int)(bi | (gi<<8) | (ri<<16)); } else color = (unsigned int)(((ri+gi+bi)>=384) ? 0xFFFFFFFF : 0); } else { // just rgb color = (unsigned int)jsvGetInteger(r); } if (isForeground) gfx.data.fgColor = color; else gfx.data.bgColor = color; graphicsSetVar(&gfx); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getColor", "generate_full" : "jswrap_graphics_getColorX(parent, true)", "return" : ["int","The integer value of the colour"] } Get the color to use for subsequent drawing operations */ /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getBgColor", "generate_full" : "jswrap_graphics_getColorX(parent, false)", "return" : ["int","The integer value of the colour"] } Get the background color to use for subsequent drawing operations */ JsVarInt jswrap_graphics_getColorX(JsVar *parent, bool isForeground) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; return (JsVarInt)((isForeground ? gfx.data.fgColor : gfx.data.bgColor) & ((1UL<1023) size=1023; } if (gfx.data.fontSize == JSGRAPHICS_FONTSIZE_CUSTOM) { jsvObjectRemoveChild(parent, JSGRAPHICS_CUSTOMFONT_BMP); jsvObjectRemoveChild(parent, JSGRAPHICS_CUSTOMFONT_WIDTH); jsvObjectRemoveChild(parent, JSGRAPHICS_CUSTOMFONT_HEIGHT); jsvObjectRemoveChild(parent, JSGRAPHICS_CUSTOMFONT_FIRSTCHAR); } gfx.data.fontSize = (short)size; graphicsSetVar(&gfx); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "setFontCustom", "generate" : "jswrap_graphics_setFontCustom", "params" : [ ["bitmap","JsVar","A column-first, MSB-first, 1bpp bitmap containing the font bitmap"], ["firstChar","int32","The first character in the font - usually 32 (space)"], ["width","JsVar","The width of each character in the font. Either an integer, or a string where each character represents the width"], ["height","int32","The height as an integer"] ] } Make subsequent calls to `drawString` use a Custom Font of the given height. See the [Fonts page](http://www.espruino.com/Fonts) for more information about custom fonts and how to create them. */ void jswrap_graphics_setFontCustom(JsVar *parent, JsVar *bitmap, int firstChar, JsVar *width, int height) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; if (!jsvIsString(bitmap)) { jsExceptionHere(JSET_ERROR, "Font bitmap must be a String"); return; } if (firstChar<0 || firstChar>255) { jsExceptionHere(JSET_ERROR, "First character out of range"); return; } if (!jsvIsString(width) && !jsvIsInt(width)) { jsExceptionHere(JSET_ERROR, "Font width must be a String or an integer"); return; } if (height<=0 || height>255) { jsExceptionHere(JSET_ERROR, "Invalid height"); return; } jsvObjectSetChild(parent, JSGRAPHICS_CUSTOMFONT_BMP, bitmap); jsvObjectSetChild(parent, JSGRAPHICS_CUSTOMFONT_WIDTH, width); jsvObjectSetChildAndUnLock(parent, JSGRAPHICS_CUSTOMFONT_HEIGHT, jsvNewFromInteger(height)); jsvObjectSetChildAndUnLock(parent, JSGRAPHICS_CUSTOMFONT_FIRSTCHAR, jsvNewFromInteger(firstChar)); gfx.data.fontSize = JSGRAPHICS_FONTSIZE_CUSTOM; graphicsSetVar(&gfx); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "drawString", "generate" : "jswrap_graphics_drawString", "params" : [ ["str","JsVar","The string"], ["x","int32","The X position of the leftmost pixel"], ["y","int32","The Y position of the topmost pixel"] ] } Draw a string of text in the current font */ void jswrap_graphics_drawString(JsVar *parent, JsVar *var, int x, int y) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; JsVar *customBitmap = 0, *customWidth = 0; int customHeight = 0, customFirstChar = 0; if (gfx.data.fontSize == JSGRAPHICS_FONTSIZE_CUSTOM) { customBitmap = jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_BMP, 0); customWidth = jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_WIDTH, 0); customHeight = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_HEIGHT, 0)); customFirstChar = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_FIRSTCHAR, 0)); } JsVar *str = jsvAsString(var, false); JsvStringIterator it; jsvStringIteratorNew(&it, str, 0); while (jsvStringIteratorHasChar(&it)) { char ch = jsvStringIteratorGetChar(&it); if (gfx.data.fontSize>0) { #ifndef SAVE_ON_FLASH int w = (int)graphicsVectorCharWidth(&gfx, gfx.data.fontSize, ch); if (x>-w && x-gfx.data.fontSize && y-4 && x-6 && y=customFirstChar) { JsvStringIterator wit; jsvStringIteratorNew(&wit, customWidth, 0); while (jsvStringIteratorHasChar(&wit) && (int)jsvStringIteratorGetIndex(&wit)<(ch-customFirstChar)) { bmpOffset += (unsigned char)jsvStringIteratorGetChar(&wit); jsvStringIteratorNext(&wit); } width = (unsigned char)jsvStringIteratorGetChar(&wit); jsvStringIteratorFree(&wit); } } else { width = (int)jsvGetInteger(customWidth); bmpOffset = width*(ch-customFirstChar); } if (ch>=customFirstChar && (x>-width) && (x-customHeight && y>3); bmpOffset &= 7; int cx,cy; for (cx=0;cx0) { #ifndef SAVE_ON_FLASH width += (int)graphicsVectorCharWidth(&gfx, gfx.data.fontSize, ch); #endif } else if (gfx.data.fontSize == JSGRAPHICS_FONTSIZE_4X6) { width += 4; } else if (gfx.data.fontSize == JSGRAPHICS_FONTSIZE_CUSTOM) { if (jsvIsString(customWidth)) { if (ch>=customFirstChar) width += (unsigned char)jsvGetCharInString(customWidth, (size_t)(ch-customFirstChar)); } else width += (int)jsvGetInteger(customWidth); } jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); jsvUnLock2(str, customWidth); return width; } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "drawLine", "generate" : "jswrap_graphics_drawLine", "params" : [ ["x1","int32","The left"], ["y1","int32","The top"], ["x2","int32","The right"], ["y2","int32","The bottom"] ] } Draw a line between x1,y1 and x2,y2 in the current foreground color */ void jswrap_graphics_drawLine(JsVar *parent, int x1, int y1, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; graphicsDrawLine(&gfx, (short)x1,(short)y1,(short)x2,(short)y2); graphicsSetVar(&gfx); // gfx data changed because modified area } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "lineTo", "generate" : "jswrap_graphics_lineTo", "params" : [ ["x","int32","X value"], ["y","int32","Y value"] ] } Draw a line from the last position of lineTo or moveTo to this position */ void jswrap_graphics_lineTo(JsVar *parent, int x, int y) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; graphicsDrawLine(&gfx, gfx.data.cursorX, gfx.data.cursorY, (short)x, (short)y); gfx.data.cursorX = (short)x; gfx.data.cursorY = (short)y; graphicsSetVar(&gfx); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "moveTo", "generate" : "jswrap_graphics_moveTo", "params" : [ ["x","int32","X value"], ["y","int32","Y value"] ] } Move the cursor to a position - see lineTo */ void jswrap_graphics_moveTo(JsVar *parent, int x, int y) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; gfx.data.cursorX = (short)x; gfx.data.cursorY = (short)y; graphicsSetVar(&gfx); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "fillPoly", "generate" : "jswrap_graphics_fillPoly", "params" : [ ["poly","JsVar","An array of vertices, of the form ```[x1,y1,x2,y2,x3,y3,etc]```"] ] } Draw a filled polygon in the current foreground color */ void jswrap_graphics_fillPoly(JsVar *parent, JsVar *poly) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; if (!jsvIsIterable(poly)) return; const int maxVerts = 128; short verts[maxVerts]; int idx = 0; JsvIterator it; jsvIteratorNew(&it, poly); while (jsvIteratorHasElement(&it) && idx0 && imageHeight>0 && imageBpp>0 && imageBpp<=32)) { jsExceptionHere(JSET_ERROR, "Expecting first argument to a valid Image"); jsvUnLock(imageBuffer); return; } JsVar *imageBufferString = jsvGetArrayBufferBackingString(imageBuffer); jsvUnLock(imageBuffer); int x=0, y=0; int bits=0; unsigned int colData = 0; JsvStringIterator it; jsvStringIteratorNew(&it, imageBufferString, 0); while ((bits>=imageBpp || jsvStringIteratorHasChar(&it)) && y>(bits-imageBpp))&imageBitMask; bits -= imageBpp; // Try and write pixel! if (!imageIsTransparent || imageTransparentCol!=col) { if (imageBpp==1) col = col ? gfx.data.fgColor : gfx.data.bgColor; graphicsSetPixel(&gfx, (short)(x+xPos), (short)(y+yPos), col); } // Go to next pixel x++; if (x>=imageWidth) { x=0; y++; // we don't care about image height - we'll stop next time... } } jsvStringIteratorFree(&it); jsvUnLock(imageBufferString); graphicsSetVar(&gfx); // gfx data changed because modified area } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getModified", "generate" : "jswrap_graphics_getModified", "params" : [ ["reset","bool","Whether to reset the modified area or not"] ], "return" : ["JsVar","An object {x1,y1,x2,y2} containing the modified area, or undefined if not modified"] } Return the area of the Graphics canvas that has been modified, and optionally clear the modified area to 0. For instance if `g.setPixel(10,20)` was called, this would return `{x1:10, y1:20, x2:10, y2:20}` */ JsVar *jswrap_graphics_getModified(JsVar *parent, bool reset) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; JsVar *obj = 0; if (gfx.data.modMinX <= gfx.data.modMaxX) { // do we have a rect? obj = jsvNewObject(); if (obj) { jsvObjectSetChildAndUnLock(obj, "x1", jsvNewFromInteger(gfx.data.modMinX)); jsvObjectSetChildAndUnLock(obj, "y1", jsvNewFromInteger(gfx.data.modMinY)); jsvObjectSetChildAndUnLock(obj, "x2", jsvNewFromInteger(gfx.data.modMaxX)); jsvObjectSetChildAndUnLock(obj, "y2", jsvNewFromInteger(gfx.data.modMaxY)); } } if (reset) { gfx.data.modMaxX = -32768; gfx.data.modMaxY = -32768; gfx.data.modMinX = 32767; gfx.data.modMinY = 32767; graphicsSetVar(&gfx); } return obj; }