├── screenshot.jpg ├── resources ├── strings │ └── strings.xml ├── bitmaps │ └── launcher_icon.png ├── settings │ └── settings.xml └── resources.xml ├── README.md ├── .gitignore ├── source ├── BasicApp.mc ├── DrawAA.mc └── BasicView.mc └── manifest.xml /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunpazed/garmin-drawaa/HEAD/screenshot.jpg -------------------------------------------------------------------------------- /resources/strings/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DrawLineAA 3 | 4 | -------------------------------------------------------------------------------- /resources/bitmaps/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunpazed/garmin-drawaa/HEAD/resources/bitmaps/launcher_icon.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DrawLineAA 2 | 3 | A quick proof-of-concept to draw anti-aliased lines and polygons on a Garmin wearable. 4 | 5 | ![](screenshot.jpg) 6 | -------------------------------------------------------------------------------- /resources/settings/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | build.sh 14 | release.sh 15 | .DS_Store 16 | .env 17 | old 18 | npm-debug.log 19 | -------------------------------------------------------------------------------- /resources/resources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DrawLineAA 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /source/BasicApp.mc: -------------------------------------------------------------------------------- 1 | using Toybox.Application as App; 2 | using Toybox.System as Sys; 3 | using Toybox.Lang as Lang; 4 | using Toybox.WatchUi as Ui; 5 | 6 | class BasicApp extends App.AppBase { 7 | 8 | 9 | function initialize() { 10 | App.AppBase.initialize(); 11 | } 12 | 13 | function onSettingsChanged() { 14 | Ui.requestUpdate(); 15 | } 16 | 17 | //! onStart() is called on application start up 18 | function onStart(state) { 19 | } 20 | 21 | //! onStop() is called when your application is exiting 22 | function onStop(state) { 23 | } 24 | 25 | //! Return the initial view of your application here 26 | function getInitialView() { 27 | return [ new BasicView() ]; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | eng 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /source/DrawAA.mc: -------------------------------------------------------------------------------- 1 | // DrawAA 2 | // -------------------------------- 3 | 4 | // This is a (poorly optimised) implementation of Xiaolin Wu's line algorithm: 5 | // https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm 6 | // 7 | // Currently only a proof-of-concept, and as such, only works 8 | // for grayscale lines on dark backgrounds 9 | // 10 | // drawAA = new DrawAA(); 11 | // drawAA.drawLine(dc, x0, y0, x1, y1); 12 | // drawAA.drawPoly(dc, poly[], offsetx, offsety); 13 | 14 | using Toybox.WatchUi as Ui; 15 | using Toybox.Graphics as Gfx; 16 | using Toybox.System as Sys; 17 | using Toybox.Lang as Lang; 18 | using Toybox.Math as Math; 19 | 20 | class DrawAA { 21 | 22 | // draw AA line from (x0,y0) to (x1,y1) on bitmap (dc) 23 | function drawLine(bitmap, x0, y0, x1, y1) { 24 | 25 | var temp; 26 | 27 | // line direction 28 | var steep = ((y1-y0).toLong().abs()) > ((x1-x0).toLong().abs()); 29 | 30 | // swap x->y co-ords if non-positive direction 31 | if (steep) { 32 | temp = x0; 33 | x0 = y0; 34 | y0 = temp; 35 | temp = x1; 36 | x1 = y1; 37 | y1 = temp; 38 | } 39 | 40 | // swap 0->1 co-ords to draw in the right order 41 | if (x0>x1) { 42 | temp = x0; 43 | x0 = x1; 44 | x1 = temp; 45 | temp = y0; 46 | y0 = y1; 47 | y1 = temp; 48 | } 49 | 50 | var dx = x1-x0; 51 | var dy = y1-y0; 52 | var gradient = (dy.toFloat() + 0.0001) / (dx.toFloat() + 0.0001); 53 | 54 | // draw first endpoint 55 | var xEnd = round(x0); 56 | var yEnd = y0+gradient*(xEnd-x0); 57 | var xGap = rfpart(x0+0.5); 58 | var xPixel1 = xEnd; 59 | var yPixel1 = ipart(yEnd); 60 | if (steep) { 61 | drawPointAA(bitmap, yPixel1, xPixel1, rfpart(yEnd)*xGap); 62 | drawPointAA(bitmap, yPixel1+1, xPixel1, fpart(yEnd)*xGap); 63 | } else { 64 | drawPointAA(bitmap, xPixel1,yPixel1, rfpart(yEnd)*xGap); 65 | drawPointAA(bitmap, xPixel1, yPixel1+1, fpart(yEnd)*xGap); 66 | } 67 | var intery = yEnd+gradient; 68 | 69 | // draw second endpoint 70 | xEnd = round(x1); 71 | yEnd = y1 + gradient * (xEnd-x1); 72 | xGap = fpart(x1+0.5); 73 | var xPixel2 = xEnd; 74 | var yPixel2 = ipart(yEnd); 75 | if (steep) { 76 | drawPointAA(bitmap, yPixel2, xPixel2, rfpart(yEnd)*xGap); 77 | drawPointAA(bitmap, yPixel2+1, xPixel2, fpart(yEnd)*xGap); 78 | } else { 79 | drawPointAA(bitmap, xPixel2, yPixel2, rfpart(yEnd)*xGap); 80 | drawPointAA(bitmap, xPixel2, yPixel2+1, fpart(yEnd)*xGap); 81 | } 82 | 83 | // draw the remainder of the line 84 | if (steep) { 85 | for(var x=xPixel1+1; x<=xPixel2-1; x++){ 86 | drawPointAA(bitmap, ipart(intery), x, rfpart(intery)); 87 | drawPointAA(bitmap, ipart(intery)+1, x, fpart(intery)); 88 | intery+=gradient; 89 | } 90 | } else { 91 | for(var x=xPixel1+1; x<=xPixel2-1; x++){ 92 | drawPointAA(bitmap, x,ipart(intery), rfpart(intery)); 93 | drawPointAA(bitmap, x, ipart(intery)+1, fpart(intery)); 94 | intery+=gradient; 95 | } 96 | } 97 | } 98 | 99 | // draw AA polygon (unfilled) from an array of (points) from offset (x,y) on bitmap (dc) 100 | function drawPoly(dc, points, x, y) { 101 | 102 | for(var p=0; p < points.size()-1; p++) { 103 | drawLine(dc, points[p][0]+x, points[p][1] + y, points[p+1][0] + x, points[p+1][1] + y); 104 | } 105 | 106 | } 107 | 108 | 109 | // helper functions for drawLine 110 | function ipart(x) { 111 | return Math.floor(x).toFloat(); 112 | } 113 | 114 | function round(x) { 115 | return ipart(x + 0.5); 116 | } 117 | 118 | function fpart(x) { 119 | return x - ipart(x); 120 | } 121 | 122 | function rfpart(x) { 123 | return 1 - fpart(x); 124 | } 125 | 126 | // draw a pixel at x,y with intensity between 0 and 1 127 | function drawPointAA(dc, x, y, intensity) { 128 | 129 | if (intensity >= 0.75) { 130 | dc.setColor(Gfx.COLOR_WHITE,0); 131 | } else if (intensity >= 0.5) { 132 | dc.setColor(Gfx.COLOR_LT_GRAY,0); 133 | } else { 134 | dc.setColor(Gfx.COLOR_DK_GRAY,0); 135 | } 136 | 137 | dc.drawPoint(x, y); 138 | 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /source/BasicView.mc: -------------------------------------------------------------------------------- 1 | using Toybox.WatchUi as Ui; 2 | using Toybox.Graphics as Gfx; 3 | using Toybox.System as Sys; 4 | using Toybox.Lang as Lang; 5 | using Toybox.Math as Math; 6 | using Toybox.Application as App; 7 | using Toybox.ActivityMonitor as ActivityMonitor; 8 | using Toybox.Timer as Timer; 9 | 10 | enum { 11 | SCREEN_SHAPE_CIRC = 0x000001, 12 | SCREEN_SHAPE_SEMICIRC = 0x000002, 13 | SCREEN_SHAPE_RECT = 0x000003 14 | } 15 | 16 | class BasicView extends Ui.WatchFace { 17 | 18 | // globals 19 | var debug = false; 20 | var timer1; 21 | var timer_timeout = 80; 22 | var timer_steps = timer_timeout; 23 | var angle = 0; 24 | var isAwake = false; 25 | var drawAA = null; 26 | 27 | // sensors / status 28 | var battery = 0; 29 | var bluetooth = true; 30 | 31 | // time 32 | var hour = null; 33 | var minute = null; 34 | var day = null; 35 | var day_of_week = null; 36 | var month_str = null; 37 | var month = null; 38 | 39 | // layout 40 | var vert_layout = false; 41 | var canvas_h = 0; 42 | var canvas_w = 0; 43 | var canvas_shape = 0; 44 | var canvas_rect = false; 45 | var canvas_circ = false; 46 | var canvas_semicirc = false; 47 | var canvas_tall = false; 48 | var canvas_r240 = false; 49 | 50 | // settings 51 | var set_leading_zero = false; 52 | 53 | // fonts 54 | 55 | // bitmaps 56 | var b_gauge = null; 57 | var b_gauge_over = null; 58 | 59 | // animation settings 60 | var poly_min; 61 | var poly_hour; 62 | var centerpoint; 63 | 64 | function initialize() { 65 | Ui.WatchFace.initialize(); 66 | } 67 | 68 | 69 | function onLayout(dc) { 70 | 71 | drawAA = new DrawAA(); 72 | 73 | // w,h of canvas 74 | canvas_w = dc.getWidth(); 75 | canvas_h = dc.getHeight(); 76 | 77 | // check the orientation 78 | if ( canvas_h > (canvas_w*1.2) ) { 79 | vert_layout = true; 80 | } else { 81 | vert_layout = false; 82 | } 83 | 84 | // let's grab the canvas shape 85 | var deviceSettings = Sys.getDeviceSettings(); 86 | canvas_shape = deviceSettings.screenShape; 87 | 88 | if (debug) { 89 | Sys.println(Lang.format("canvas_shape: $1$", [canvas_shape])); 90 | } 91 | 92 | // find out the type of screen on the device 93 | canvas_tall = (vert_layout && canvas_shape == SCREEN_SHAPE_RECT) ? true : false; 94 | canvas_rect = (canvas_shape == SCREEN_SHAPE_RECT && !vert_layout) ? true : false; 95 | canvas_circ = (canvas_shape == SCREEN_SHAPE_CIRC) ? true : false; 96 | canvas_semicirc = (canvas_shape == SCREEN_SHAPE_SEMICIRC) ? true : false; 97 | canvas_r240 = (canvas_w == 240 && canvas_w == 240) ? true : false; 98 | 99 | // set offsets based on screen type 100 | // positioning for different screen layouts 101 | if (canvas_tall) { 102 | } 103 | if (canvas_rect) { 104 | } 105 | if (canvas_circ) { 106 | if (canvas_r240) { 107 | } else { 108 | } 109 | } 110 | if (canvas_semicirc) { 111 | } 112 | 113 | 114 | // w,h of canvas 115 | var dw = dc.getWidth(); 116 | var dh = dc.getHeight(); 117 | 118 | // define the polygon points for the minute and hour hands 119 | poly_min = [[dw/2,dh/2], [(dw/2)-10,(dh/2)-20], [(dw/2),5], [(dw/2)+10,(dh/2)-20], [dw/2,dh/2] ]; 120 | poly_hour = [[dw/2,dh/2], [(dw/2)-10,(dh/2)-20], [(dw/2),35], [(dw/2)+10,(dh/2)-20], [dw/2,dh/2] ]; 121 | 122 | // centerpoint is the middle of the canvas 123 | centerpoint = [dw/2,dh/2]; 124 | 125 | } 126 | 127 | 128 | 129 | // helper function to rotate point[x,y] around origin[x,y] by (angle) 130 | function rotatePoint(origin, point, angle) { 131 | 132 | var radians = angle * Math.PI / 180.0; 133 | var cos = Math.cos(radians); 134 | var sin = Math.sin(radians); 135 | var dX = point[0] - origin[0]; 136 | var dY = point[1] - origin[1]; 137 | 138 | return [ cos * dX - sin * dY + origin[0], sin * dX + cos * dY + origin[1]]; 139 | 140 | } 141 | 142 | 143 | //! Called when this View is brought to the foreground. Restore 144 | //! the state of this View and prepare it to be shown. This includes 145 | //! loading resources into memory. 146 | function onShow() { 147 | } 148 | 149 | 150 | //! Update the view 151 | function onUpdate(dc) { 152 | 153 | 154 | // grab time objects 155 | var clockTime = Sys.getClockTime(); 156 | var date = Time.Gregorian.info(Time.now(),0); 157 | 158 | // define time, day, month variables 159 | hour = clockTime.hour; 160 | minute = clockTime.min; 161 | day = date.day; 162 | month = date.month; 163 | day_of_week = Time.Gregorian.info(Time.now(), Time.FORMAT_MEDIUM).day_of_week; 164 | month_str = Time.Gregorian.info(Time.now(), Time.FORMAT_MEDIUM).month; 165 | 166 | // grab battery 167 | var stats = Sys.getSystemStats(); 168 | var batteryRaw = stats.battery; 169 | battery = batteryRaw > batteryRaw.toNumber() ? (batteryRaw + 1).toNumber() : batteryRaw.toNumber(); 170 | 171 | // do we have bluetooth? 172 | var deviceSettings = Sys.getDeviceSettings(); 173 | bluetooth = deviceSettings.phoneConnected; 174 | 175 | // 12-hour support 176 | if (hour > 12 || hour == 0) { 177 | if (!deviceSettings.is24Hour) 178 | { 179 | if (hour == 0) 180 | { 181 | hour = 12; 182 | } 183 | else 184 | { 185 | hour = hour - 12; 186 | } 187 | } 188 | } 189 | 190 | // add padding to units if required 191 | if( minute < 10 ) { 192 | minute = "0" + minute; 193 | } 194 | 195 | if( hour < 10 && set_leading_zero) { 196 | hour = "0" + hour; 197 | } 198 | 199 | if( day < 10 ) { 200 | day = "0" + day; 201 | } 202 | 203 | if( month < 10 ) { 204 | month = "0" + month; 205 | } 206 | 207 | 208 | // clear the screen 209 | dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_BLACK); 210 | dc.clear(); 211 | 212 | 213 | var offsetx = 0; 214 | var offsety = 0; 215 | 216 | var poly_min_rot = []; 217 | var poly_hour_rot = []; 218 | 219 | var temp_point = []; 220 | 221 | // rotate the poly_hour[] and poly_min[] hands around the centerpoint[x,y] by (angle) 222 | for (var u=0; u