/* * 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 "jswrap_functions.h" // for asURL #include "bitmap_font_4x6.h" #include "bitmap_font_6x8.h" #ifndef SAVE_ON_FLASH #ifndef ESPRUINOBOARD #define GRAPHICS_DRAWIMAGE_ROTATED #endif #endif #if defined(LINUX) || defined(BANGLEJS) #define GRAPHICS_FAST_PATHS // execute more optimised code when no rotation/etc #endif #ifdef GRAPHICS_PALETTED_IMAGES // 16 color MAC OS palette const uint16_t PALETTE_4BIT[16] = { 0x0,0x4228,0x8c51,0xbdd7,0x9b26,0x6180,0x320,0x540,0x4df,0x19,0x3013,0xf813,0xd800,0xfb20,0xffe0,0xffff }; // 256 color 16 bit Web-safe palette const uint16_t PALETTE_8BIT[256] = { 0x0,0x6,0xc,0x13,0x19,0x1f,0x180,0x186,0x18c,0x193,0x199,0x19f,0x320,0x326,0x32c,0x333,0x339,0x33f,0x4c0, 0x4c6,0x4cc,0x4d3,0x4d9,0x4df,0x660,0x666,0x66c,0x673,0x679,0x67f,0x7e0,0x7e6,0x7ec,0x7f3,0x7f9,0x7ff, 0x3000,0x3006,0x300c,0x3013,0x3019,0x301f,0x3180,0x3186,0x318c,0x3193,0x3199,0x319f,0x3320,0x3326,0x332c, 0x3333,0x3339,0x333f,0x34c0,0x34c6,0x34cc,0x34d3,0x34d9,0x34df,0x3660,0x3666,0x366c,0x3673,0x3679,0x367f, 0x37e0,0x37e6,0x37ec,0x37f3,0x37f9,0x37ff,0x6000,0x6006,0x600c,0x6013,0x6019,0x601f,0x6180,0x6186,0x618c, 0x6193,0x6199,0x619f,0x6320,0x6326,0x632c,0x6333,0x6339,0x633f,0x64c0,0x64c6,0x64cc,0x64d3,0x64d9,0x64df, 0x6660,0x6666,0x666c,0x6673,0x6679,0x667f,0x67e0,0x67e6,0x67ec,0x67f3,0x67f9,0x67ff,0x9800,0x9806,0x980c, 0x9813,0x9819,0x981f,0x9980,0x9986,0x998c,0x9993,0x9999,0x999f,0x9b20,0x9b26,0x9b2c,0x9b33,0x9b39,0x9b3f, 0x9cc0,0x9cc6,0x9ccc,0x9cd3,0x9cd9,0x9cdf,0x9e60,0x9e66,0x9e6c,0x9e73,0x9e79,0x9e7f,0x9fe0,0x9fe6,0x9fec, 0x9ff3,0x9ff9,0x9fff,0xc800,0xc806,0xc80c,0xc813,0xc819,0xc81f,0xc980,0xc986,0xc98c,0xc993,0xc999,0xc99f, 0xcb20,0xcb26,0xcb2c,0xcb33,0xcb39,0xcb3f,0xccc0,0xccc6,0xcccc,0xccd3,0xccd9,0xccdf,0xce60,0xce66,0xce6c, 0xce73,0xce79,0xce7f,0xcfe0,0xcfe6,0xcfec,0xcff3,0xcff9,0xcfff,0xf800,0xf806,0xf80c,0xf813,0xf819,0xf81f, 0xf980,0xf986,0xf98c,0xf993,0xf999,0xf99f,0xfb20,0xfb26,0xfb2c,0xfb33,0xfb39,0xfb3f,0xfcc0,0xfcc6,0xfccc, 0xfcd3,0xfcd9,0xfcdf,0xfe60,0xfe66,0xfe6c,0xfe73,0xfe79,0xfe7f,0xffe0,0xffe6,0xffec,0xfff3,0xfff9,0xffff, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffff }; #endif /*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); jsvObjectSetChild(execInfo.hiddenRoot, JS_GRAPHICS_VAR, parentObj); 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 } /*JSON{ "type" : "staticmethod", "class" : "Graphics", "name" : "getInstance", "generate" : "jswrap_graphics_getInstance", "return" : ["JsVar","An instance of `Graphics` or undefined"] } On devices like Pixl.js or HYSTM boards that contain a built-in display this will return an instance of the graphics class that can be used to access that display. Internally, this is stored as a member called `gfx` inside the 'hiddenRoot'. */ JsVar *jswrap_graphics_getInstance() { return jsvObjectGetChild(execInfo.hiddenRoot, JS_GRAPHICS_VAR, 0); } 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", "interleavex = Pixels 0,2,4,etc are from the top half of the image, 1,3,5,etc from the bottom half. Used for P3 LED panels.", "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, "interleavex", 0))) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_ARRAYBUFFER_INTERLEAVEX); if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "vertical_byte", 0))) { if (gfx.data.bpp==1) gfx.data.flags = (JsGraphicsFlags)(gfx.data.flags | JSGRAPHICSFLAGS_ARRAYBUFFER_VERTICAL_BYTE); else { jsExceptionHere(JSET_ERROR, "vertical_byte only works for 1bpp ArrayBuffers\n"); return 0; } if (gfx.data.height&7) { jsExceptionHere(JSET_ERROR, "height must be a multiple of 8 when using vertical_byte\n"); return 0; } } 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", "ifndef" : "SAVE_ON_FLASH", "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"], ["bpp","int32","Bits per pixel (8,16,24 or 32 supported)"] ], "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, int bpp) { 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 = bpp; lcdInit_SDL(&gfx); graphicsSetVar(&gfx); return parent; } #endif /*JSON{ "type" : "staticmethod", "class" : "Graphics", "name" : "createImage", "#if" : "!defined(SAVE_ON_FLASH) && !defined(ESPRUINOBOARD)", "generate" : "jswrap_graphics_createImage", "params" : [ ["str","JsVar","A String containing a newline-separated image - space is 0, anything else is 1"] ], "return" : ["JsVar","An Image object that can be used with `Graphics.drawImage`"] } Create a simple Black and White image for use with `Graphics.drawImage`. Use as follows: ``` var img = Graphics.createImage(` XXXXXXXXX X X X X X X X X X X XXXXXXXXX `); g.drawImage(img, x,y); ``` If the characters at the beginning and end of the string are newlines, they will be ignored. Spaces are treated as `0`, and any other character is a `1` */ JsVar *jswrap_graphics_createImage(JsVar *data) { if (!jsvIsString(data)) { jsExceptionHere(JSET_TYPEERROR, "Expecting a String"); return 0; } int x=0,y=0; int width=0, height=0; size_t startCharacter = 0; JsvStringIterator it; // First iterate and work out width and height jsvStringIteratorNew(&it,data,0); while (jsvStringIteratorHasChar(&it)) { char ch = jsvStringIteratorGetChar(&it); if (ch=='\n') { if (x==0 && y==0) startCharacter = 1; // ignore first character x=0; y++; } else { if (y>=height) height=y+1; x++; if (x>width) width=x; } jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); // Sorted - now create the object, set it up and create the buffer JsVar *img = jsvNewObject(); if (!img) return 0; jsvObjectSetChildAndUnLock(img,"width",jsvNewFromInteger(width)); jsvObjectSetChildAndUnLock(img,"height",jsvNewFromInteger(height)); // bpp is 1, no need to set it int len = (width*height+7)>>3; JsVar *buffer = jsvNewStringOfLength((unsigned)len, NULL); if (!buffer) { // not enough memory jsvUnLock(img); return 0; } // Now set the characters! x=0; y=0; jsvStringIteratorNew(&it,data,startCharacter); while (jsvStringIteratorHasChar(&it)) { char ch = jsvStringIteratorGetChar(&it); if (ch=='\n') { x=0; y++; } else { if (ch!=' ') { /* a pixel to set. This'll be slowish for non-flat strings, * but this is here for relatively small bitmaps * anyway so it's not a big deal */ size_t idx = (size_t)(y*width + x); jsvSetCharInString(buffer, idx>>3, (char)(128>>(idx&7)), true/*OR*/); } x++; } jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); jsvObjectSetChildAndUnLock(img, "buffer", buffer); return img; } /*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", "params" : [ ["reset","bool","If `true`, resets the state of Graphics to the default (eg. Color, Font, etc)"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Clear the LCD with the Background Color */ JsVar *jswrap_graphics_clear(JsVar *parent, bool resetState) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; if (resetState) graphicsStructResetState(&gfx); graphicsClear(&gfx); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "fillRect", "generate" : "jswrap_graphics_fillRect", "params" : [ ["x1","int32","The left X coordinate"], ["y1","int32","The top Y coordinate"], ["x2","int32","The right X coordinate"], ["y2","int32","The bottom Y coordinate"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Fill a rectangular area in the Foreground Color */ JsVar *jswrap_graphics_fillRect(JsVar *parent, int x1, int y1, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; graphicsFillRect(&gfx, x1,y1,x2,y2,gfx.data.fgColor); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "clearRect", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_clearRect", "params" : [ ["x1","int32","The left X coordinate"], ["y1","int32","The top Y coordinate"], ["x2","int32","The right X coordinate"], ["y2","int32","The bottom Y coordinate"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Fill a rectangular area in the Background Color */ JsVar *jswrap_graphics_clearRect(JsVar *parent, int x1, int y1, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; graphicsFillRect(&gfx, x1,y1,x2,y2,gfx.data.bgColor); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "drawRect", "generate" : "jswrap_graphics_drawRect", "params" : [ ["x1","int32","The left X coordinate"], ["y1","int32","The top Y coordinate"], ["x2","int32","The right X coordinate"], ["y2","int32","The bottom Y coordinate"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw an unfilled rectangle 1px wide in the Foreground Color */ JsVar *jswrap_graphics_drawRect(JsVar *parent, int x1, int y1, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; graphicsDrawRect(&gfx, x1,y1,x2,y2); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "fillCircle", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_fillCircle", "params" : [ ["x","int32","The X axis"], ["y","int32","The Y axis"], ["rad","int32","The circle radius"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw a filled circle in the Foreground Color */ JsVar *jswrap_graphics_fillCircle(JsVar *parent, int x, int y, int rad) { jswrap_graphics_fillEllipse(parent, x-rad, y-rad, x+rad, y+rad); return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "drawCircle", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_drawCircle", "params" : [ ["x","int32","The X axis"], ["y","int32","The Y axis"], ["rad","int32","The circle radius"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw an unfilled circle 1px wide in the Foreground Color */ JsVar *jswrap_graphics_drawCircle(JsVar *parent, int x, int y, int rad) { jswrap_graphics_drawEllipse(parent, x-rad, y-rad, x+rad, y+rad); return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "fillEllipse", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_fillEllipse", "params" : [ ["x1","int32","The left X coordinate"], ["y1","int32","The top Y coordinate"], ["x2","int32","The right X coordinate"], ["y2","int32","The bottom Y coordinate"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw a filled ellipse in the Foreground Color */ JsVar *jswrap_graphics_fillEllipse(JsVar *parent, int x, int y, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; graphicsFillEllipse(&gfx, x,y,x2,y2); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "drawEllipse", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_drawEllipse", "params" : [ ["x1","int32","The left X coordinate"], ["y1","int32","The top Y coordinate"], ["x2","int32","The right X coordinate"], ["y2","int32","The bottom Y coordinate"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw an ellipse in the Foreground Color */ JsVar *jswrap_graphics_drawEllipse(JsVar *parent, int x, int y, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; graphicsDrawEllipse(&gfx, x,y,x2,y2); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*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, x, 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"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Set a pixel's color */ JsVar *jswrap_graphics_setPixel(JsVar *parent, int x, int y, JsVar *color) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; unsigned int col = gfx.data.fgColor; if (!jsvIsUndefined(color)) col = (unsigned int)jsvGetInteger(color); graphicsSetPixel(&gfx, x, y, col); gfx.data.cursorX = (short)x; gfx.data.cursorY = (short)y; graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*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)"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Set the color to use for subsequent drawing operations. If just `r` is specified as an integer, the numeric value will be written directly into a pixel. eg. On a 24 bit `Graphics` instance you set bright blue with either `g.setColor(0,0,1)` or `g.setColor(0x0000FF)`. The mapping is as follows: * 32 bit: `r,g,b` => `0xFFrrggbb` * 24 bit: `r,g,b` => `0xrrggbb` * 16 bit: `r,g,b` => `0brrrrrggggggbbbbb` (RGB565) * Other bpp: `r,g,b` => white if `r+g+b > 50%`, otherwise black (use `r` on its own as an integer) If you specified `color_order` when creating the `Graphics` instance, `r`,`g` and `b` will be swapped as you specified. **Note:** On devices with low flash memory, `r` **must** be an integer representing the color in the current bit depth. It cannot be a floating point value, and `g` and `b` are ignored. */ /*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)"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Set the background color to use for subsequent drawing operations. See `Graphics.setColor` for more information on the mapping of `r`, `g`, and `b` to pixel values. **Note:** On devices with low flash memory, `r` **must** be an integer representing the color in the current bit depth. It cannot be a floating point value, and `g` and `b` are ignored. */ JsVar *jswrap_graphics_setColorX(JsVar *parent, JsVar *r, JsVar *g, JsVar *b, bool isForeground) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; unsigned int color = 0; #ifdef SAVE_ON_FLASH if (false) { #else 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)); #if defined(GRAPHICS_PALETTED_IMAGES) && LCD_BPP==4 } else if (gfx.data.bpp==4) { // LCD is paletted - look up in our palette to find the best match int d = 0x7FFFFFFF; color = 0; for (int i=0;i<16;i++) { int p = PALETTE_4BIT[i]; int pr = (p>>8)&0xF8; int pg = (p>>3)&0xFC; int pb = (p<<3)&0xF8; pr |= pr>>5; pg |= pb>>6; pb |= pb>>5; int dr = pr-ri; int dg = pg-gi; int db = pb-bi; int dd = dr*dr+dg*dg+db*db; if (dd=384) ? 0xFFFFFFFF : 0); #endif } else { // just rgb color = (unsigned int)jsvGetInteger(r); } if (isForeground) gfx.data.fgColor = color; else gfx.data.bgColor = color; graphicsSetVar(&gfx); return jsvLockAgain(parent); } /*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_FONT_MASK) == 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 = (unsigned short)size; graphicsSetVar(&gfx); #endif return jsvLockAgain(parent); } /*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 (max 255). Bits 8-15 represent the scale factor (eg. `2<<8` is twice the size)"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } 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. */ JsVar *jswrap_graphics_setFontCustom(JsVar *parent, JsVar *bitmap, int firstChar, JsVar *width, int height) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; if (!jsvIsString(bitmap)) { jsExceptionHere(JSET_ERROR, "Font bitmap must be a String"); return 0; } if (firstChar<0 || firstChar>255) { jsExceptionHere(JSET_ERROR, "First character out of range"); return 0; } if (!jsvIsString(width) && !jsvIsInt(width)) { jsExceptionHere(JSET_ERROR, "Font width must be a String or an integer"); return 0; } int scale = height>>8; if (scale<1) scale=1; height = height&255; 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 = (unsigned short)(scale + JSGRAPHICS_FONTSIZE_CUSTOM); graphicsSetVar(&gfx); return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "setFontAlign", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_setFontAlign", "params" : [ ["x","int32","X alignment. -1=left (default), 0=center, 1=right"], ["y","int32","Y alignment. -1=top (default), 0=center, 1=bottom"], ["rotation","int32","Rotation of the text. 0=normal, 1=90 degrees clockwise, 2=180, 3=270"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Set the alignment for subsequent calls to `drawString` */ JsVar *jswrap_graphics_setFontAlign(JsVar *parent, int x, int y, int r) { #ifndef SAVE_ON_FLASH JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; if (x<-1) x=-1; if (x>1) x=1; if (y<-1) y=-1; if (y>1) y=1; if (r<0) r=0; if (r>3) r=3; gfx.data.fontAlignX = x; gfx.data.fontAlignY = y; gfx.data.fontRotate = r; graphicsSetVar(&gfx); return jsvLockAgain(parent); #else return 0; #endif } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "setFont", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_setFont", "params" : [ ["name","JsVar","The name of the current font"], ["size","int","The size of the font"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Set the font by name, eg "4x6", or "Vector12". For bitmap fonts you can also specify a size multiplier, for example `g.setFont("4x6",2)` will double the size of the standard 4x6 bitmap font to 8x12. */ JsVar *jswrap_graphics_setFont(JsVar *parent, JsVar *name, int size) { #ifndef SAVE_ON_FLASH if (!jsvIsString(name)) return 0; JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; unsigned short sz = 0xFFFF; #ifndef NO_VECTOR_FONT if (jsvIsStringEqualOrStartsWith(name, "Vector", true)) { sz = (unsigned short)jsvGetIntegerAndUnLock(jsvNewFromStringVar(name, 6, JSVAPPENDSTRINGVAR_MAXLENGTH)); if (size>0) sz = (unsigned short)size; } #endif if (size<=0) size=1; if (size>JSGRAPHICS_FONTSIZE_SCALE_MASK) size=JSGRAPHICS_FONTSIZE_SCALE_MASK; if (jsvIsStringEqual(name, "4x6")) sz = (unsigned short)(size + JSGRAPHICS_FONTSIZE_4X6); #ifdef USE_FONT_6X8 if (jsvIsStringEqual(name, "6x8")) sz = (unsigned short)(size + JSGRAPHICS_FONTSIZE_6X8); #endif // TODO: if function named 'setFontXYZ' exists, run it if (sz==0xFFFF) { jsExceptionHere(JSET_ERROR, "Unknown font %j", name); } gfx.data.fontSize=sz; graphicsSetVar(&gfx); return jsvLockAgain(parent); #else return 0; #endif } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getFont", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_getFont", "return" : ["JsVar","Get the name of the current font"], "return_object" : "String" } Get the font by name - can be saved and used with `Graphics.setFont` */ JsVar *jswrap_graphics_getFont(JsVar *parent) { #ifndef SAVE_ON_FLASH JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; JsGraphicsFontSize f = gfx.data.fontSize & JSGRAPHICS_FONTSIZE_FONT_MASK; #ifndef NO_VECTOR_FONT if (f == JSGRAPHICS_FONTSIZE_VECTOR) return jsvVarPrintf("Vector%d",gfx.data.fontSize & JSGRAPHICS_FONTSIZE_SCALE_MASK); #endif if (f == JSGRAPHICS_FONTSIZE_4X6) return jsvNewFromString("4x6"); #ifdef USE_FONT_6X8 if (f == JSGRAPHICS_FONTSIZE_6X8) return jsvNewFromString("6x8"); #endif if (f == JSGRAPHICS_FONTSIZE_CUSTOM) { // not implemented yet because it's painful trying to pass 5 arguments into setFontCustom /*JsVar *n = jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_NAME, 0); if (n) return n;*/ return jsvNewFromString("Custom"); } return jsvNewFromInteger(gfx.data.fontSize); #else return 0; #endif } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getFonts", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_getFonts", "return" : ["JsVar","And array of font names"], "return_object" : "Array" } Return an array of all fonts currently in the Graphics library. **Note:** Vector fonts are specified as `Vector#` where `#` is the font height. As there are effectively infinite fonts, just `Vector` is included in the list. */ JsVar *jswrap_graphics_getFonts(JsVar *parent) { #ifndef SAVE_ON_FLASH JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; JsVar *arr = jsvNewEmptyArray(); if (!arr) return 0; jsvArrayPushAndUnLock(arr, jsvNewFromString("4x6")); #ifdef USE_FONT_6X8 jsvArrayPushAndUnLock(arr, jsvNewFromString("6x8")); #endif #ifndef NO_VECTOR_FONT jsvArrayPushAndUnLock(arr, jsvNewFromString("Vector")); #endif JsVar *c = jspGetPrototype(parent); JsvObjectIterator it; jsvObjectIteratorNew(&it, c); while (jsvObjectIteratorHasValue(&it)) { JsVar *k = jsvObjectIteratorGetKey(&it); if (jsvIsStringEqualOrStartsWith(k,"setFont",true)) jsvArrayPushAndUnLock(arr, jsvNewFromStringVar(k,7,JSVAPPENDSTRINGVAR_MAXLENGTH)); jsvUnLock(k); jsvObjectIteratorNext(&it); } jsvObjectIteratorFree(&it); jsvUnLock(c); return arr; #else return 0; #endif } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getFontHeight", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_getFontHeight", "return" : ["int","The height in pixels of the current font"] } Return the height in pixels of the current font */ static int jswrap_graphics_getFontHeightInternal(JsGraphics *gfx) { JsGraphicsFontSize f = gfx->data.fontSize & JSGRAPHICS_FONTSIZE_FONT_MASK; unsigned short scale = gfx->data.fontSize & JSGRAPHICS_FONTSIZE_SCALE_MASK; if (f == JSGRAPHICS_FONTSIZE_VECTOR) { return scale; } else if (f == JSGRAPHICS_FONTSIZE_4X6) { return 6*scale; #ifdef USE_FONT_6X8 } else if (f == JSGRAPHICS_FONTSIZE_6X8) { return 8*scale; #endif } else if (f == JSGRAPHICS_FONTSIZE_CUSTOM) { return scale*(int)jsvGetIntegerAndUnLock(jsvObjectGetChild(gfx->graphicsVar, JSGRAPHICS_CUSTOMFONT_HEIGHT, 0)); } return 0; } int jswrap_graphics_getFontHeight(JsVar *parent) { #ifndef SAVE_ON_FLASH JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; return jswrap_graphics_getFontHeightInternal(&gfx); #else return 0; #endif } /*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"], ["solid","bool","For bitmap fonts, should empty pixels be filled with the background color?"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw a string of text in the current font */ JsVar *jswrap_graphics_drawString(JsVar *parent, JsVar *var, int x, int y, bool solidBackground) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; JsVar *customBitmap = 0, *customWidth = 0; int customHeight = jswrap_graphics_getFontHeightInternal(&gfx); int customFirstChar = 0; JsGraphicsFontSize font = gfx.data.fontSize & JSGRAPHICS_FONTSIZE_FONT_MASK; unsigned short scale = gfx.data.fontSize & JSGRAPHICS_FONTSIZE_SCALE_MASK; if (font == JSGRAPHICS_FONTSIZE_CUSTOM) { customBitmap = jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_BMP, 0); customWidth = jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_WIDTH, 0); customFirstChar = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_FIRSTCHAR, 0)); } #ifndef SAVE_ON_FLASH // Handle text rotation JsGraphicsFlags oldFlags = gfx.data.flags; if (gfx.data.fontRotate==1) { gfx.data.flags ^= JSGRAPHICSFLAGS_SWAP_XY | JSGRAPHICSFLAGS_INVERT_X; int t = gfx.data.width - (x+1); x = y; y = t; } else if (gfx.data.fontRotate==2) { gfx.data.flags ^= JSGRAPHICSFLAGS_INVERT_X | JSGRAPHICSFLAGS_INVERT_Y; x = gfx.data.width - (x+1); y = gfx.data.height - (y+1); } else if (gfx.data.fontRotate==3) { gfx.data.flags ^= JSGRAPHICSFLAGS_SWAP_XY | JSGRAPHICSFLAGS_INVERT_Y; int t = gfx.data.height - (y+1); y = x; x = t; } // Handle font alignment if (gfx.data.fontAlignX<2) // 0=center, 1=right, 2=undefined, 3=left x -= jswrap_graphics_stringWidth(parent, var) * (gfx.data.fontAlignX+1)/2; if (gfx.data.fontAlignY<2) // 0=center, 1=bottom, 2=undefined, 3=top y -= customHeight * (gfx.data.fontAlignY+1)/2; #endif int maxX = (gfx.data.flags & JSGRAPHICSFLAGS_SWAP_XY) ? gfx.data.height : gfx.data.width; int maxY = (gfx.data.flags & JSGRAPHICSFLAGS_SWAP_XY) ? gfx.data.width : gfx.data.height; int startx = x; JsVar *str = jsvAsString(var); JsvStringIterator it; jsvStringIteratorNew(&it, str, 0); while (jsvStringIteratorHasChar(&it)) { char ch = jsvStringIteratorGetChar(&it); if (ch=='\n') { x = startx; y += customHeight; jsvStringIteratorNext(&it); continue; } if (font == JSGRAPHICS_FONTSIZE_VECTOR) { #ifndef NO_VECTOR_FONT int w = (int)graphicsVectorCharWidth(&gfx, gfx.data.fontSize, ch); if (x>-w && x-gfx.data.fontSize && y-4 && x-6 && y-6 && x-8 && 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;cxgraphicsVar, s, x, y, false),s); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "stringWidth", "generate" : "jswrap_graphics_stringWidth", "params" : [ ["str","JsVar","The string"] ], "return" : ["int","The length of the string in pixels"] } Return the size in pixels of a string of text in the current font */ JsVarInt jswrap_graphics_stringWidth(JsVar *parent, JsVar *var) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; JsVar *customWidth = 0; int customFirstChar = 0; JsGraphicsFontSize font = gfx.data.fontSize & JSGRAPHICS_FONTSIZE_FONT_MASK; unsigned short scale = gfx.data.fontSize & JSGRAPHICS_FONTSIZE_SCALE_MASK; if (font == JSGRAPHICS_FONTSIZE_CUSTOM) { customWidth = jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_WIDTH, 0); customFirstChar = (int)jsvGetIntegerAndUnLock(jsvObjectGetChild(parent, JSGRAPHICS_CUSTOMFONT_FIRSTCHAR, 0)); } JsVar *str = jsvAsString(var); JsvStringIterator it; jsvStringIteratorNew(&it, str, 0); int width = 0; int maxWidth = 0; while (jsvStringIteratorHasChar(&it)) { char ch = jsvStringIteratorGetChar(&it); if (ch=='\n') { if (width>maxWidth) maxWidth=width; width = 0; } if (font == JSGRAPHICS_FONTSIZE_VECTOR) { #ifndef NO_VECTOR_FONT width += (int)graphicsVectorCharWidth(&gfx, scale, ch); #endif } else if (font == JSGRAPHICS_FONTSIZE_4X6) { width += 4*scale; #ifdef USE_FONT_6X8 } else if (font == JSGRAPHICS_FONTSIZE_6X8) { width += 6*scale; #endif } else if (font == JSGRAPHICS_FONTSIZE_CUSTOM) { if (jsvIsString(customWidth)) { if (ch>=customFirstChar) width += scale*(unsigned char)jsvGetCharInString(customWidth, (size_t)(ch-customFirstChar)); } else width += scale*(int)jsvGetInteger(customWidth); } jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); jsvUnLock2(str, customWidth); return width>maxWidth ? width : maxWidth; } /*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"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw a line between x1,y1 and x2,y2 in the current foreground color */ JsVar *jswrap_graphics_drawLine(JsVar *parent, int x1, int y1, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; graphicsDrawLine(&gfx, x1,y1,x2,y2); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "lineTo", "generate" : "jswrap_graphics_lineTo", "params" : [ ["x","int32","X value"], ["y","int32","Y value"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw a line from the last position of lineTo or moveTo to this position */ JsVar *jswrap_graphics_lineTo(JsVar *parent, int x, int y) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; graphicsDrawLine(&gfx, gfx.data.cursorX, gfx.data.cursorY, x, y); gfx.data.cursorX = (short)x; gfx.data.cursorY = (short)y; graphicsSetVar(&gfx); return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "moveTo", "generate" : "jswrap_graphics_moveTo", "params" : [ ["x","int32","X value"], ["y","int32","Y value"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Move the cursor to a position - see lineTo */ JsVar *jswrap_graphics_moveTo(JsVar *parent, int x, int y) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; gfx.data.cursorX = (short)x; gfx.data.cursorY = (short)y; graphicsSetVar(&gfx); return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "drawPoly", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_drawPoly", "params" : [ ["poly","JsVar","An array of vertices, of the form ```[x1,y1,x2,y2,x3,y3,etc]```"], ["closed","bool","Draw another line between the last element of the array and the first"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw a polyline (lines between each of the points in `poly`) in the current foreground color */ JsVar *jswrap_graphics_drawPoly(JsVar *parent, JsVar *poly, bool closed) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; if (!jsvIsIterable(poly)) return 0; int x,y; int startx, starty; int idx = 0; JsvIterator it; jsvIteratorNew(&it, poly, JSIF_EVERY_ARRAY_ELEMENT); while (jsvIteratorHasElement(&it)) { int el = jsvIteratorGetIntegerValue(&it); if (idx&1) { y = el; if (idx==1) { // save xy positions of first point startx = x; starty = y; } else { // only start drawing between the first 2 points graphicsDrawLine(&gfx, gfx.data.cursorX, gfx.data.cursorY, x, y); } gfx.data.cursorX = (short)x; gfx.data.cursorY = (short)y; } else x = el; idx++; jsvIteratorNext(&it); } jsvIteratorFree(&it); // if closed, draw between first and last points if (closed) graphicsDrawLine(&gfx, gfx.data.cursorX, gfx.data.cursorY, startx, starty); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "fillPoly", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_fillPoly", "params" : [ ["poly","JsVar","An array of vertices, of the form ```[x1,y1,x2,y2,x3,y3,etc]```"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Draw a filled polygon in the current foreground color */ JsVar *jswrap_graphics_fillPoly(JsVar *parent, JsVar *poly) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; if (!jsvIsIterable(poly)) return 0; const int maxVerts = 128; short verts[maxVerts]; int idx = 0; JsvIterator it; jsvIteratorNew(&it, poly, JSIF_EVERY_ARRAY_ELEMENT); while (jsvIteratorHasElement(&it) && idxvarData.arraybuffer.type==ARRAYBUFFERVIEW_UINT16) { size_t l = 0; palettePtr = (uint16_t *)jsvGetDataPointer(v, &l); jsvUnLock(v); if (l==2 || l==4 || l==16) paletteMask = l-1; else { palettePtr = 0; } } else jsvUnLock(v); if (!palettePtr) { jsExceptionHere(JSET_ERROR, "palette specified, but must be a flat Uint16Array of 2,4, or 16 elements"); return 0; } } imageBuffer = jsvObjectGetChild(image, "buffer", 0); imageBufferOffset = 0; } else if (jsvIsString(image) || jsvIsArrayBuffer(image)) { if (jsvIsArrayBuffer(image)) { imageBuffer = jsvGetArrayBufferBackingString(image); } else { imageBuffer = jsvLockAgain(image); } if (!jsvIsString(imageBuffer)) { jsvUnLock(imageBuffer); return 0; } imageWidth = (unsigned char)jsvGetCharInString(imageBuffer,0); imageHeight = (unsigned char)jsvGetCharInString(imageBuffer,1); imageBpp = (unsigned char)jsvGetCharInString(imageBuffer,2); if (imageBpp & 128) { imageBpp = imageBpp&127; imageIsTransparent = true; imageTransparentCol = (unsigned char)jsvGetCharInString(imageBuffer,3); imageBufferOffset = 4; } else { imageBufferOffset = 3; } } else { jsExceptionHere(JSET_ERROR, "Expecting first argument to be an object or a String"); return 0; } if (!imageIsTransparent) imageTransparentCol = 0xFFFFFFFF; if (palettePtr==0) { if (imageBpp==1) { simplePalette[0] = gfx.data.bgColor; simplePalette[1] = gfx.data.fgColor; palettePtr = simplePalette; paletteMask = 1; #ifdef GRAPHICS_PALETTED_IMAGES } else if (gfx.data.bpp==16 && imageBpp==2) { // Blend from bg to fg int b = gfx.data.bgColor; int br = (b>>8)&0xF8; int bg = (b>>3)&0xFC; int bb = (b<<3)&0xF8; int f = gfx.data.fgColor; int fr = (f>>8)&0xF8; int fg = (f>>3)&0xFC; int fb = (f<<3)&0xF8; simplePalette[0] = gfx.data.bgColor; int ri,gi,bi; ri = (br*2 + fr)/3; gi = (bg*2 + fg)/3; bi = (bb*2 + fb)/3; simplePalette[1] = (bi>>3) | (gi>>2)<<5 | (ri>>3)<<11; ri = (br + fr*2)/3; gi = (bg + fg*2)/3; bi = (bb + fb*2)/3; simplePalette[2] = (bi>>3) | (gi>>2)<<5 | (ri>>3)<<11; simplePalette[3] = gfx.data.fgColor; palettePtr = simplePalette; paletteMask = 3; } else if (gfx.data.bpp==16 && imageBpp==4) { // palette is 16 bits, so don't use it for other things palettePtr = PALETTE_4BIT; paletteMask = 15; } else if (gfx.data.bpp==16 && imageBpp==8) { // palette is 16 bits, so don't use it for other things palettePtr = PALETTE_8BIT; paletteMask = 255; #endif } } if (!(jsvIsArrayBuffer(imageBuffer) || jsvIsString(imageBuffer)) || imageWidth<=0 || imageHeight<=0 || imageBpp>32) { jsExceptionHere(JSET_ERROR, "Expecting first argument to a valid Image"); jsvUnLock(imageBuffer); return 0; } unsigned int imageBitMask = (unsigned int)((1L<>(bits-imageBpp))&imageBitMask; bits -= imageBpp; // Try and write pixel! if (imageTransparentCol!=col) { if (palettePtr) col = palettePtr[col&paletteMask]; if (xp>=0 && xp=0 && yp=gfx.data.width) x2 = gfx.data.width - 1; if (y2>=gfx.data.height) y2 = gfx.data.height - 1; if (x1 < gfx.data.modMinX) gfx.data.modMinX=(short)x1; if (x2 > gfx.data.modMaxX) gfx.data.modMaxX=(short)x2; if (y1 < gfx.data.modMinY) gfx.data.modMinY=(short)y1; if (y2 > gfx.data.modMaxY) gfx.data.modMaxY=(short)y2; } else { // handle rotation, and default to center the image #else if (true) { #endif for (y=0;y>(bits-imageBpp))&imageBitMask; bits -= imageBpp; // Try and write pixel! if (imageTransparentCol!=col) { if (palettePtr) col = palettePtr[col&paletteMask]; graphicsSetPixel(&gfx, x+xPos, y+yPos, col); } } } } } else if (jsvIsObject(options)) { #ifndef GRAPHICS_DRAWIMAGE_ROTATED jsExceptionHere(JSET_ERROR,"Image rotation not implemented on this device"); #else // fancy rotation/scaling int imageStride = (imageWidth*imageBpp + 7)>>3; // rotate, scale, centerx, centery double scale = jsvGetFloatAndUnLock(jsvObjectGetChild(options,"scale",0)); if (!isfinite(scale) || scale<=0) scale=1; double rotate = jsvGetFloatAndUnLock(jsvObjectGetChild(options,"rotate",0)); bool rotateIsSet = isfinite(rotate); if (!rotateIsSet) rotate = 0; #ifdef GRAPHICS_FAST_PATHS bool fastPath = (!rotateIsSet) && // not rotating (scale-floor(scale))==0 && // integer scale (gfx.data.flags & (JSGRAPHICSFLAGS_SWAP_XY|JSGRAPHICSFLAGS_INVERT_X|JSGRAPHICSFLAGS_INVERT_Y))==0; // no messing with coordinates if (fastPath) { // fast path for non-rotated, integer scale int s = (int)scale; // Scaled blitting /* Output as scanlines rather than small fillrects so * that on direct-coupled displays we can optimise away * coordinate setting */ int yp = yPos; for (y=0;y>(bits-imageBpp))&imageBitMask; bits -= imageBpp; // Try and write pixel! if (imageTransparentCol!=col && yp>=0 && yp=0 && xp=gfx.data.width) x2 = gfx.data.width - 1; if (y2>=gfx.data.height) y2 = gfx.data.height - 1; if (x1 < gfx.data.modMinX) gfx.data.modMinX=(short)x1; if (x2 > gfx.data.modMaxX) gfx.data.modMaxX=(short)x2; if (y1 < gfx.data.modMinY) gfx.data.modMinY=(short)y1; if (y2 > gfx.data.modMaxY) gfx.data.modMaxY=(short)y2; } else { // handle rotation, and default to center the image #else if (true) { #endif int centerx = imageWidth*128; int centery = imageHeight*128; JsVar *v; v = jsvObjectGetChild(options,"centerx",0); if (v) centerx = jsvGetIntegerAndUnLock(v)*256; v = jsvObjectGetChild(options,"centery",0); if (v) centery = jsvGetIntegerAndUnLock(v)*256; // step values for blitting rotated image double vcos = cos(rotate); double vsin = sin(rotate); int sx = (int)((vcos/scale)*256 + 0.5); int sy = (int)((vsin/scale)*256 + 0.5); // work out actual image width and height int iw = (int)(0.5 + scale*(imageWidth*fabs(vcos) + imageHeight*fabs(vsin))); int ih = (int)(0.5 + scale*(imageWidth*fabs(vsin) + imageHeight*fabs(vcos))); // if rotating, offset our start position from center if (rotateIsSet) { xPos -= iw/2; yPos -= ih/2; } // work out start position in the image int px = centerx - (1 + (sx*iw) + (sy*ih)) / 2; int py = centery - (1 + (sx*ih) - (sy*iw)) / 2; // scan across image for (y=0;y>8; int imagey = (qy+127)>>8; if (imagex>=0 && imagey>=0 && imagex>3)); colData = (unsigned char)jsvStringIteratorGetChar(&it); for (int b=8;b>((imagePixelsPerByteMask-(bitOffset&imagePixelsPerByteMask))*imageBpp)) & imageBitMask; } if (imageTransparentCol!=colData) { if (palettePtr) colData = palettePtr[colData&paletteMask]; graphicsSetPixel(&gfx, x+xPos, y+yPos, colData); } } qx += sx; qy -= sy; } px += sy; py += sx; } } #endif } jsvStringIteratorFree(&it); jsvUnLock(imageBufferString); graphicsSetVar(&gfx); // gfx data changed because modified area return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "asImage", "ifndef" : "SAVE_ON_FLASH", "generate" : "jswrap_graphics_asImage", "return" : ["JsVar","An Image that can be used with `Graphics.drawImage`"] } Return this Graphics object as an Image that can be used with `Graphics.drawImage`. Will return undefined if data can't be allocated for it. */ JsVar *jswrap_graphics_asImage(JsVar *parent) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; JsVar *img = jsvNewObject(); if (!img) return 0; int w = jswrap_graphics_getWidthOrHeight(parent,false); int h = jswrap_graphics_getWidthOrHeight(parent,true); int bpp = gfx.data.bpp; jsvObjectSetChildAndUnLock(img,"width",jsvNewFromInteger(w)); jsvObjectSetChildAndUnLock(img,"height",jsvNewFromInteger(h)); if (bpp!=1) jsvObjectSetChildAndUnLock(img,"bpp",jsvNewFromInteger(bpp)); int len = (w*h*bpp+7)>>3; JsVar *buffer = jsvNewStringOfLength((unsigned)len, NULL); if (!buffer) { // not enough memory jsvUnLock(img); return 0; } int x=0, y=0; unsigned int pixelBits = 0; unsigned int pixelBitCnt = 0; JsvStringIterator it; jsvStringIteratorNew(&it, buffer, 0); while (jsvStringIteratorHasChar(&it)) { pixelBits = (pixelBits<=w) { x=0; y++; } while (pixelBitCnt>=8) { jsvStringIteratorSetCharAndNext(&it, (char)(pixelBits>>(pixelBitCnt-8))); pixelBits = pixelBits>>8; pixelBitCnt -= 8; } } jsvStringIteratorFree(&it); jsvObjectSetChildAndUnLock(img,"buffer",buffer); return img; } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "getModified", "ifndef" : "SAVE_ON_FLASH", "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) { #ifndef SAVE_ON_FLASH 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; #else return 0; #endif } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "scroll", "#if" : "!defined(SAVE_ON_FLASH) && !defined(ESPRUINOBOARD)", "generate" : "jswrap_graphics_scroll", "params" : [ ["x","int32","X direction. >0 = to right"], ["y","int32","Y direction. >0 = down"] ], "return" : ["JsVar","The instance of Graphics this was called on, to allow call chaining"], "return_object" : "Graphics" } Scroll the contents of this graphics in a certain direction. The remaining area is filled with the background color. Note: This uses repeated pixel reads and writes, so will not work on platforms that don't support pixel reads. */ JsVar *jswrap_graphics_scroll(JsVar *parent, int xdir, int ydir) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; graphicsScroll(&gfx, xdir, ydir); // update modified area graphicsSetVar(&gfx); return jsvLockAgain(parent); } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "asBMP", "#if" : "!defined(SAVE_ON_FLASH) && !defined(ESPRUINOBOARD)", "generate" : "jswrap_graphics_asBMP", "return" : ["JsVar","A String representing the Graphics as a Windows BMP file (or 'undefined' if not possible)"] } Create a Windows BMP file from this Graphics instance, and return it as a String. */ JsVar *jswrap_graphics_asBMP(JsVar *parent) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0; if (gfx.data.bpp!=1 && gfx.data.bpp!=24) { jsExceptionHere(JSET_ERROR, "asBMP/asURL only works on 1bpp/24bpp Graphics"); return 0; } int width = (gfx.data.flags & JSGRAPHICSFLAGS_SWAP_XY) ? gfx.data.height : gfx.data.width; int height = (gfx.data.flags & JSGRAPHICSFLAGS_SWAP_XY) ? gfx.data.width : gfx.data.height; int rowstride = (((width*gfx.data.bpp)+31) >> 5) << 2; // padded to 32 bits bool hasPalette = gfx.data.bpp==1; int headerLen = 14+ 12+ (hasPalette?6:0); int l = headerLen + height*rowstride; JsVar *imgData = jsvNewFlatStringOfLength((unsigned)l); if (!imgData) return 0; // not enough memory unsigned char *imgPtr = (unsigned char *)jsvGetFlatStringPointer(imgData); imgPtr[0]=66; imgPtr[1]=77; imgPtr[2]=(unsigned char)l; imgPtr[3]=(unsigned char)(l>>8); // plus 2 more bytes for size imgPtr[10]=(unsigned char)headerLen; // BITMAPCOREHEADER imgPtr[14]=12; // sizeof(BITMAPCOREHEADER) imgPtr[18]=(unsigned char)width; imgPtr[19]=(unsigned char)(width>>8); imgPtr[20]=(unsigned char)height; imgPtr[21]=(unsigned char)(height>>8); imgPtr[22]=1; imgPtr[24]=(unsigned char)gfx.data.bpp; // bpp if (hasPalette) { imgPtr[26]=255; imgPtr[27]=255; imgPtr[28]=255; } for (int y=0;y>3) - 1] = (unsigned char)b; } } else { for (int x=0;x>3)); imgPtr[i++] = (unsigned char)(c); imgPtr[i++] = (unsigned char)(c>>8); imgPtr[i++] = (unsigned char)(c>>16); } } } return imgData; } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "asURL", "#if" : "!defined(SAVE_ON_FLASH) && !defined(ESPRUINOBOARD)", "generate" : "jswrap_graphics_asURL", "return" : ["JsVar","A String representing the Graphics as a URL (or 'undefined' if not possible)"] } Create a URL of the form `data:image/bmp;base64,...` that can be pasted into the browser. The Espruino Web IDE can detect this data on the console and render the image inline automatically. */ JsVar *jswrap_graphics_asURL(JsVar *parent) { JsVar *imgData = jswrap_graphics_asBMP(parent); if (!imgData) return 0; // not enough memory JsVar *b64 = jswrap_btoa(imgData); jsvUnLock(imgData); if (!b64) return 0; // not enough memory JsVar *r = jsvVarPrintf("data:image/bmp;base64,%v",b64); jsvUnLock(b64); return r; } /*JSON{ "type" : "method", "class" : "Graphics", "name" : "dump", "#if" : "!defined(SAVE_ON_FLASH) && !defined(ESPRUINOBOARD)", "generate" : "jswrap_graphics_dump" } Output this image as a bitmap URL. The Espruino Web IDE can detect the data on the console and render the image inline automatically. This is identical to `console.log(g.asURL())` - it is just a convenient function for easy debugging. */ void jswrap_graphics_dump(JsVar *parent) { JsVar *url = jswrap_graphics_asURL(parent); if (url) jsiConsolePrintStringVar(url); jsvUnLock(url); jsiConsolePrint("\n"); }