void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const
{
    fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize);
}
void Graphics::fillRoundedRectangle (const Rectangle<float>& r, const float cornerSize) const
{
    fillRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize);
}
void ArduRCT_Graphics::_executeMacroCommand(ardurct_graphicsMacroCommand_t *mc, int16_t x, int16_t y, uint16_t scaleMul, uint16_t scaleDiv) {
    uint8_t group = mc->cmd & GRAPHICS_MACRO_CMD_GROUP_MASK;

    // presets
    if (group == GRAPHICS_MACRO_CMD_GROUP_PRESET) {
        if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_FOREGROUND) _mForegroundColor = mc->color;
        else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_BACKGROUND) _mBackgroundColor = mc->color;
        else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_THICKNESS) {
            _mThickness = mc->param[GRAPHICS_MACRO_PARAM_THICKNESS];
            _mIsThicknessScalable = mc->param[GRAPHICS_MACRO_PARAM_THICKNESS_IS_SCALABLE] != 0;
        } else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_ERASE) fillScreen(_mBackgroundColor);
        else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_FONT) {
            _mFontSize = mc->param[GRAPHICS_MACRO_PARAM_FONT_SIZE];
            _mIsFontBold = (mc->param[GRAPHICS_MACRO_PARAM_FONT_IS_BOLD] != 0);
            _mIsFontOverlay = (mc->param[GRAPHICS_MACRO_PARAM_FONT_IS_OVERLAY] != 0);
        } else if (mc->cmd == GRAPHICS_MACRO_CMD_PRESET_SCALE) {
            _mScaleMul = mc->param[GRAPHICS_MACRO_PARAM_SCALE_MUL];
            _mScaleDiv = 1;
            if ((mc->nbParams > 1) && (mc->param[GRAPHICS_MACRO_PARAM_SCALE_DIV] != 0)) _mScaleDiv = mc->param[GRAPHICS_MACRO_PARAM_SCALE_DIV];
        }
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.println("preset");    
#endif
        return;
    }
    
    int32_t sMul = _mScaleMul;
    sMul = sMul * (scaleMul == 0 ? 1 : scaleMul);
    int32_t sDiv = _mScaleDiv;
    sDiv = sDiv * (scaleDiv == 0 ? 1 : scaleDiv);
    int32_t sX = mc->param[GRAPHICS_MACRO_PARAM_X1];
    sX = sX * sMul/sDiv + x;
    int32_t sY = mc->param[GRAPHICS_MACRO_PARAM_Y1];        
    sY = sY * sMul/sDiv + y;        
    int32_t sThickness = _mThickness;
    if (_mIsThicknessScalable) sThickness = sThickness * sMul/sDiv;    
    
    // lines
    if (group == GRAPHICS_MACRO_CMD_GROUP_LINE) {
        int32_t sX2, sY2;
        if (mc->nbParams < 2) return;
        if (mc->cmd == GRAPHICS_MACRO_CMD_LINE) {
            if (mc->nbParams > 2) {
                sX2 = mc->param[GRAPHICS_MACRO_PARAM_X2];
                sX2 = x + sX2 * sMul/sDiv;
                sY2 = mc->param[GRAPHICS_MACRO_PARAM_Y2];
                sY2 = y + sY2 * sMul/sDiv;
                // p2 becomes the end point
                _mX = mc->param[GRAPHICS_MACRO_PARAM_X2];
                _mY = mc->param[GRAPHICS_MACRO_PARAM_Y2];
            } else {
                sX2 = sX;
                sY2 = sY;
                sX = _mX;
                sX = x + sX * sMul/sDiv;
                sY = _mY;    
                sY = y + sY * sMul/sDiv;    
                // p1 becomes the end point
                _mX = mc->param[GRAPHICS_MACRO_PARAM_X1];
                _mY = mc->param[GRAPHICS_MACRO_PARAM_Y1];
            }
        } else {
            // delta values
            if (mc->nbParams > 2) {
                // p2 = p1+delta
                sX2 = mc->param[GRAPHICS_MACRO_PARAM_X1] + mc->param[GRAPHICS_MACRO_PARAM_X2];
                sX2 = x + sX2 * sMul/sDiv;
                sY2 = mc->param[GRAPHICS_MACRO_PARAM_Y1] + mc->param[GRAPHICS_MACRO_PARAM_Y2];
                sY2 = y + sY2 * sMul/sDiv;
                // p1+delta becomes the end point
                _mX = mc->param[GRAPHICS_MACRO_PARAM_X1] + mc->param[GRAPHICS_MACRO_PARAM_X2];
                _mY = mc->param[GRAPHICS_MACRO_PARAM_Y1] + mc->param[GRAPHICS_MACRO_PARAM_Y2];
            } else {
                // p2 = old_point+delta
                sX2 = _mX + mc->param[GRAPHICS_MACRO_PARAM_X1];
                sX2 = x + sX2 * sMul/sDiv;
                sY2 = _mY + mc->param[GRAPHICS_MACRO_PARAM_Y1];
                sY2 = y + sY2 * sMul/sDiv;
                sX = _mX;
                sX = x + sX * sMul/sDiv;
                sY = _mY;    
                sY = y + sY * sMul/sDiv;    
                // old_point+delta  becomes the end point
                _mX += mc->param[GRAPHICS_MACRO_PARAM_X1];
                _mY += mc->param[GRAPHICS_MACRO_PARAM_Y1];
            }
            
        }
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("line "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); 
            Serial.print(sX2); Serial.print(" "); Serial.println(sY2);
#endif
        drawLine(sX, sY, sX2, sY2, _mForegroundColor, sThickness, false);
        return;
    }    
    
    // arcs
    if (group == GRAPHICS_MACRO_CMD_GROUP_ARC) {
        if (mc->nbParams < 1) return;
        boolean reversed = (mc->cmd == GRAPHICS_MACRO_CMD_ARC_REVERSED) || (mc->cmd == GRAPHICS_MACRO_CMD_ARC_FILLED_REVERSED);
        int32_t sRadius = 0;
        if (mc->nbParams == 1) {
            sRadius = mc->param[GRAPHICS_MACRO_PARAM_ARC_1];
            sRadius = sRadius * sMul/sDiv;
            int32_t sArcStartX = _getArcEnd(sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], !reversed, true);
            int32_t sArcStartY = _getArcEnd(sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], !reversed, false);
            int32_t sArcEndX = _getArcEnd(sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], reversed, true);
            int32_t sArcEndY = _getArcEnd(sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], reversed, false);
            sX = _mX;
            sX = x + sX * sMul/sDiv - sArcStartX;
            sY = _mY;
            sY = y + sY * sMul/sDiv - sArcStartY;
            _mX += sArcEndX - sArcStartX;
            _mY += sArcEndY - sArcStartY;

        } else if (mc->nbParams == 3) {
            sRadius = mc->param[GRAPHICS_MACRO_PARAM_ARC_3];
            sRadius = sRadius * sMul/sDiv;
            _mX = mc->param[GRAPHICS_MACRO_PARAM_X1] + _getArcEnd(mc->param[GRAPHICS_MACRO_PARAM_ARC_3], mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], reversed, true);
            _mY = mc->param[GRAPHICS_MACRO_PARAM_Y1] + _getArcEnd(mc->param[GRAPHICS_MACRO_PARAM_ARC_3], mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], reversed, false);
        } else return;
        if ((mc->cmd == GRAPHICS_MACRO_CMD_ARC_REVERSED) || (mc->cmd == GRAPHICS_MACRO_CMD_ARC)) 
            drawArc(sX, sY, sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], _mForegroundColor, sThickness, false);
        else fillArc(sX, sY, sRadius, mc->param[GRAPHICS_MACRO_PARAM_ARC_OCTANT], _mForegroundColor, false);            
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("arc "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); Serial.println(sRadius);
#endif
        return;
    }
    
    // circles
    if (group == GRAPHICS_MACRO_CMD_GROUP_CIRCLE) {
        // we need at least 3 parameters
        if (mc->nbParams < 3) return;
        int32_t sRadius = mc->param[GRAPHICS_MACRO_PARAM_RADIUS];
        sRadius = sRadius * sMul/sDiv;
        if (mc->cmd == GRAPHICS_MACRO_CMD_CIRCLE) drawCircle(sX, sY, sRadius, _mForegroundColor, sThickness, false);
        else fillCircle(sX, sY, sRadius, _mForegroundColor, false);
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("circle "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); Serial.println(sRadius);
#endif
        return;
    }
    
    // rectangles
    if (group == GRAPHICS_MACRO_CMD_GROUP_RECTANGLE) {
        // we need at least 4 parameters
        if (mc->nbParams < 4) return;
        int32_t sWidth = mc->param[GRAPHICS_MACRO_PARAM_WIDTH];
        sWidth = sWidth * sMul/sDiv;
        int32_t sHeight = mc->param[GRAPHICS_MACRO_PARAM_HEIGHT];
        sHeight = sHeight * sMul/sDiv;
        if (mc->cmd == GRAPHICS_MACRO_CMD_RECTANGLE) drawRectangle(sX, sY, sWidth, sHeight, _mForegroundColor, sThickness, false);
        else if (mc->cmd == GRAPHICS_MACRO_CMD_RECTANGLE_FILLED) fillRectangle(sX, sY, sWidth, sHeight, _mForegroundColor, false);
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("rectangle "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); Serial.print(sWidth); Serial.print(" "); Serial.println(sHeight);
#endif
        // we need at least 1 more parameter to display the rounded rectangle
        if (mc->nbParams < 5) return;
        int32_t sRadius = mc->param[GRAPHICS_MACRO_PARAM_ROUNDING];
        sRadius = sRadius * sMul/sDiv;
        if (mc->cmd == GRAPHICS_MACRO_CMD_RECTANGLE_ROUNDED) drawRoundedRectangle(sX, sY, sWidth, sHeight, sRadius, _mForegroundColor, sThickness, false);
        else if (mc->cmd == GRAPHICS_MACRO_CMD_RECTANGLE_ROUNDED_FILLED) fillRoundedRectangle(sX, sY, sWidth, sHeight, sRadius, _mForegroundColor, false);
        return;
    }
    
    // triangles
    if (group == GRAPHICS_MACRO_CMD_GROUP_TRIANGLE) {
        // we need at least 6 parameters
        if (mc->nbParams < 6) return;
        int32_t sX2 = mc->param[GRAPHICS_MACRO_PARAM_X2];
        sX2 = x + sX2 * sMul/sDiv;
        int32_t sY2 = mc->param[GRAPHICS_MACRO_PARAM_Y2];
        sY2 = y + sY2 * sMul/sDiv;
        int32_t sX3 = mc->param[GRAPHICS_MACRO_PARAM_X3];
        sX3 = x + sX3 * sMul/sDiv;
        int32_t sY3 = mc->param[GRAPHICS_MACRO_PARAM_Y3];
        sY3 = y + sY3 * sMul/sDiv;
        if (mc->cmd == GRAPHICS_MACRO_CMD_TRIANGLE) drawTriangle(sX, sY, sX2, sY2, sX3, sY3, _mForegroundColor, sThickness, false);
        else if (mc->cmd == GRAPHICS_MACRO_CMD_TRIANGLE_FILLED) fillTriangle(sX, sY, sX2, sY2, sX3, sY3, _mForegroundColor, false);
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("triangle "); Serial.print(sX); Serial.print(" "); Serial.print(sY); Serial.print(" "); Serial.print(sX2); Serial.print(" "); Serial.print(sY2); 
            Serial.print(" "); Serial.print(sX3); Serial.print(" "); Serial.println(sY3);
#endif
        return;
    }
        
    // strings
    if (mc->cmd == GRAPHICS_MACRO_CMD_STRING) {
        if (mc->nbParams < 2) return;
        int bc = _backgroundColor;
        if (!_mIsFontOverlay) setBackgroundColor(_mBackgroundColor);
        drawString((char *)mc->text, sX, sY, _mForegroundColor, _mFontSize, _mIsFontBold, _mIsFontOverlay, false);
        if (!_mIsFontOverlay) setBackgroundColor(bc);
#ifdef GRAPHICS_MACRO_DEBUG
        Serial.print("text "); Serial.println((char *)mc->text);
#endif

        return;
    }
    
    // executes
    if (group == GRAPHICS_MACRO_CMD_GROUP_EXECUTE) {
        if (mc->param[GRAPHICS_MACRO_PARAM_MACRO_NUMBER] >= GRAPHICS_MACRO_MAX_NUMBER) return;
        // read the length in the EEPROM allocation table
        int length = eeprom_read_uint8_t(mc->param[GRAPHICS_MACRO_PARAM_MACRO_NUMBER]);
        if (length == 0xFF) return;
        // read the EEPROM pointers table to get the start
        int start = GRAPHICS_MACRO_MAX_NUMBER + mc->param[GRAPHICS_MACRO_PARAM_MACRO_NUMBER] * GRAPHICS_MACRO_MAX_SIZE;
        // get the compressed macro
        uint8_t buffer[GRAPHICS_MACRO_MAX_SIZE];
        uint8_t i=0;
        while (i < length) {
            buffer[i] = eeprom_read_uint8_t(start+i);
            i++;
        }
        buffer[i] = 0;
        ardurct_graphicsMacroCommand_t emc;
        // uncompress the macro commands and execute them
        if (mc->cmd == GRAPHICS_MACRO_CMD_EXECUTE_WITH_RESET) _initializeMacros();
        i = 0;
        while (i < length) {
            int8_t len = _uncompressMacroCommand(buffer, i, &emc);
            if (len == GRAPHICS_MACRO_FORMAT_ERROR) return;
            _executeMacroCommand(&emc, x, y, scaleMul, scaleDiv);
            i = len;
        }
    }
}