diff --git a/Backend/Database/PNGPlotter.cpp b/Backend/Database/PNGPlotter.cpp index 83ea2c4..610388f 100644 --- a/Backend/Database/PNGPlotter.cpp +++ b/Backend/Database/PNGPlotter.cpp @@ -4,21 +4,21 @@ using namespace shmea; -PNGPlotter::PNGPlotter(unsigned width, unsigned height, int max_candles, double max_price, double low_price, int lines) +PNGPlotter::PNGPlotter(unsigned width, unsigned height, int max_candles, double max_price, double low_price, int lines, int margin_top, int margin_right, int margin_bottom, int margin_left) : image(), width(width), height(height), min_price(low_price), max_price(max_price), - old_min_price(min_price), - old_max_price(max_price), - last_candle_pos(0), + margin_top(margin_top), + margin_right(margin_right), + margin_bottom(margin_bottom), + margin_left(margin_left), last_timestamp(0), total_candles_drawn(0), max_candles(max_candles), - margin_x(20), - margin_y(20), - candle_width(width / max_candles), + candle_width(static_cast(max_candles != 0 ? (width - margin_left - margin_right) / max_candles : 1)), + last_candle_pos(static_cast(candle_width / 2)), lines(lines), - first_line_point(lines, true), + first_line_point(lines, false), last_price_pos(lines, 0), last_line_drawn(0), line_colors(), @@ -27,114 +27,111 @@ PNGPlotter::PNGPlotter(unsigned width, unsigned height, int max_candles, double color_bearish(255, 0, 0, 255) { image.Allocate(width, height); - RGBA white(255, 255, 255, 255); - image.SetAllPixels(white); + RGBA DarkGray(0x40, 0x40, 0x40, 0xFF); + RGBA Black(0x00, 0x00, 0x00, 0x7F); + image.drawVerticalGradient(0, 0, DarkGray, Black, 0); + + initialize_colors(line_colors, line_color_names); +// drawGrid(); +} +void PNGPlotter::drawGrid(int numHorizontalLines, int numVerticalLines) { + RGBA gridColor(200, 200, 200, 200); // Light gray for the grid lines -/* if(lines != 0) - generateUniqueColors(lines);*/ + // Draw horizontal grid lines and y-axis labels + for (int i = 0; i <= numHorizontalLines; ++i) { + int y = i * (height - margin_top - margin_bottom) / numHorizontalLines; + drawLine(margin_left, y, width - margin_right, y, gridColor); - initialize_colors(line_colors, line_color_names); +/* // Draw y-axis labels if provided + if (!yLabels.empty() && i < yLabels.size()) { + drawText(margin_x - 30, y, yLabels[i]); // Adjust x-position for label placement + }*/ + } + + // Draw vertical grid lines and x-axis labels + for (int i = 0; i <= numVerticalLines; ++i) { + int x = i * (width - margin_left - margin_right) / numVerticalLines; + drawLine(x, 0, x, height - margin_top - margin_bottom, gridColor); + +/* // Draw x-axis labels if provided + if (!xLabels.empty() && i < xLabels.size()) { + drawText(x, height - margin_y + 15, xLabels[i]); // Adjust y-position for label placement + }*/ + } } void PNGPlotter::initialize_colors(std::vector& lines_colors, std::vector& lines_colors_name) { - - lines_colors.push_back(RGBA(0, 0, 255, 255)); // Blue + // TODO: Find a way to auto generate these, for now there are just 10 + lines_colors.push_back(RGBA(0x00, 0x00, 0xFF, 0xFF)); // Blue lines_colors_name.push_back("Blue"); - lines_colors.push_back(RGBA(255, 165, 0, 255)); // Orange + lines_colors.push_back(RGBA(0xFF, 0xA5, 0x00, 0xFF)); // Orange lines_colors_name.push_back("Orange"); - lines_colors.push_back(RGBA(128, 0, 128, 255)); // Purple + lines_colors.push_back(RGBA(0x80, 0x00, 0x80, 0xFF)); // Purple lines_colors_name.push_back("Purple"); - lines_colors.push_back(RGBA(0, 255, 255, 255)); // Cyan + lines_colors.push_back(RGBA(0x00, 0xFF, 0xFF, 0xFF)); // Cyan lines_colors_name.push_back("Cyan"); - lines_colors.push_back(RGBA(255, 255, 0, 255)); // Yellow + lines_colors.push_back(RGBA(0xFF, 0xFF, 0x00, 0xFF)); // Yellow lines_colors_name.push_back("Yellow"); - lines_colors.push_back(RGBA(255, 0, 255, 255)); // Magenta + lines_colors.push_back(RGBA(0xFF, 0x00, 0xFF, 0xFF)); // Magenta lines_colors_name.push_back("Magenta"); - lines_colors.push_back(RGBA(0, 128, 128, 255)); // Teal + lines_colors.push_back(RGBA(0x00, 0x80, 0x80, 0xFF)); // Teal lines_colors_name.push_back("Teal"); - lines_colors.push_back(RGBA(255, 140, 0, 255)); // Dark Orange + lines_colors.push_back(RGBA(0xFF, 0x8C, 0x00, 0xFF)); // Dark Orange lines_colors_name.push_back("Dark Orange"); - lines_colors.push_back(RGBA(255, 105, 180, 255)); // Pink + lines_colors.push_back(RGBA(0xFF, 0x69, 0xB4, 0xFF)); // Pink lines_colors_name.push_back("Pink"); - lines_colors.push_back(RGBA(173, 216, 230, 255)); // Light Blue + lines_colors.push_back(RGBA(0xAD, 0xD8, 0xE6, 0xFF)); // Light Blue lines_colors_name.push_back("Light Blue"); - - -} -/* - -Comment these out because it might be useful in the future, but for now it's easier to hardcode colors - -//Helper function to convert from hue to RGB -float hueToRGB(float p, float q, float t) -{ - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1.0f / 6) return p + (q - p) * 6 * t; - if (t < 1.0f / 2) return q; - if (t < 2.0f / 3) return p + (q - p) * (2.0f / 3 - t) * 6; + // Remember to add colour entries of indicator when adding new indicators + indicatorColors["BBLow"] = RGBA(0x00, 0x00, 0xFF, 0xFF); // Blue + indicatorColors["BBHigh"] = RGBA(0x00, 0x00, 0xFF, 0xFF); // Blue + indicatorColors["EMA"] = RGBA(0xFF, 0xFF, 0x00, 0xFF); // Yellow + indicatorColors["SMA"] = RGBA(0x80, 0x00, 0x80, 0xFF); // Purple +} - return p; -} -PNGPlotter::RGB PNGPlotter::HSLToRGB(float h, float s, float l) +void PNGPlotter::addDataPointWithIndicator(double newPrice, int portIndex, std::string indicator, std::string value) { - float r, g, b; + //TODO: ERROR CHECK value when you implement it + if (indicator.empty()) { + printf("Error: You need to define an indicator to draw a line.\n"); + return; + } - if (s == 0) - { - r = g = b = 1; //Achromatic gray - } - else - { - float q = l < 0.5f ? l * (1 + s) : l + s - l * s; - float p = 2 * l - q; - r = hueToRGB(p, q, h + 1.0f / 3); - g = hueToRGB(p, q, h); - b = hueToRGB(p, q, h - 1.0f / 3); - } - //Convert to RGB [0, 255] - PNGPlotter::RGB rgb; - rgb.r = static_cast(r * 255); - rgb.g = static_cast(g * 255); - rgb.b = static_cast(b * 255); + if (indicatorColors.find(indicator) == indicatorColors.end()) + { + printf("Error: The specified indicator '%s' is not recognized.\n", indicator.c_str()); + return; + } - return rgb; -} + addDataPoint(newPrice, portIndex, true, &indicatorColors[indicator]); -void PNGPlotter::generateUniqueColors(int x) -{ - float saturation = 0.7f; //Fixed saturation - float lightness = 0.5f; - for(int i = 0; i < x; ++i) - { - //Spread hue values evenly across the color wheel - float hue = static_cast(i) / x; +} - //convert HSL to RGB - PNGPlotter::RGB rgb = HSLToRGB(hue, saturation, lightness); - line_colors.push_back(RGBA(rgb.r, rgb.g, rgb.b, 255)); - - } -} */ +inline int clamp(int value, int min, int max) +{ + if (value < min) return min; + if (value > max) return max; + return value; +} -void PNGPlotter::addDataPoint(double newPrice, int portIndex, bool draw) +void PNGPlotter::addDataPoint(double newPrice, int portIndex, bool draw, RGBA* lineColor) { if(lines == 0) - { + { printf("PNG Plotter not initialized with the correct amount of lines to draw"); return; } @@ -144,224 +141,196 @@ void PNGPlotter::addDataPoint(double newPrice, int portIndex, bool draw) printf("PNG Plotter does not have the index for this line assigned"); return; } - float y = height - ((newPrice - min_price) / (max_price - min_price) * (height - 2 * margin_y)) - margin_y; - - if(!first_line_point[portIndex] && draw) - drawLine(last_line_drawn - 1, last_price_pos[portIndex], last_line_drawn, y, portIndex); - - - //update the previous-y coordinate - last_price_pos[portIndex] = y; - - //Only update X once all the lines have been drawn - if(portIndex == lines - 1) - last_line_drawn += candle_width; - //set the first_point for the specific line to false after the first data point is plotted - first_line_point[portIndex] = false; - //drawCandleStick(image, x, y_open, y_close, y_high, y_low, raw_close >= raw_open ? color_bullish : color_bearish); - -} - -inline int roundFloat(float val) -{ - return static_cast(val + (val >= 0 ? 0.5f : -0.5f)); -} - - -float ipart(float x) { - return std::floor(x); -} - -float fpart(float x) { - return x - std::floor(x); -} - -float rfpart(float x) { - return 1.0f - fpart(x); -} - -// Plot function to blend colors based on brightness -void PNGPlotter::plot(float x, float y, float brightness, int portIndex) { - RGBA color = line_colors[portIndex]; - RGBA pixelColor = image.GetPixel(static_cast(x), static_cast(y)); + if(lineColor == NULL) + { + lineColor = &line_colors[portIndex]; + } - unsigned char blendedR = static_cast(color.r * brightness + pixelColor.r * (1 - brightness)); - unsigned char blendedG = static_cast(color.g * brightness + pixelColor.g * (1 - brightness)); - unsigned char blendedB = static_cast(color.b * brightness + pixelColor.b * (1 - brightness)); + int y = height - margin_bottom - static_cast((newPrice - min_price) / (max_price - min_price) * (height - margin_top - margin_bottom)); - image.SetPixel(static_cast(x), static_cast(y), RGBA(blendedR, blendedG, blendedB, 255)); -} + y = clamp(y, margin_top, height - margin_bottom); -// Xiaolin Wu's Line Algorithm with Thickness (C++98 version) -void PNGPlotter::drawLine(float x1, float y1, float x2, float y2, int portIndex) { - // Local variable for line thickness - int thickness = candle_width; // Adjust the thickness value here + if(draw) + { + if(!first_line_point[portIndex]) + { + first_line_point[portIndex] = true; + last_line_drawn = -1 * static_cast(candle_width / 2); //This is done so that when we add by candle_width below, the starting x will be in the middle of the candle stick + } + else + { + int startX = last_line_drawn + margin_left; + int endX = last_line_drawn + candle_width + margin_left; - bool steep = std::abs(y2 - y1) > std::abs(x2 - x1); - - if (steep) { - // Swap x and y if the line is steep - std::swap(x1, y1); - std::swap(x2, y2); - } + drawLine(startX, last_price_pos[portIndex], endX, y, *lineColor); + } - if (x1 > x2) { - // Ensure we always draw from left to right - std::swap(x1, x2); - std::swap(y1, y2); + //update the previous-y coordinate + last_price_pos[portIndex] = y; } - float dx = x2 - x1; - float dy = y2 - y1; - float gradient = (dx == 0) ? 1 : dy / dx; - - // Handle the first endpoint - float xEnd = roundFloat(x1); - - float yEnd = y1 + gradient * (xEnd - x1); - float xGap = rfpart(x1 + 0.5f); - float xPixel1 = xEnd; - float yPixel1 = ipart(yEnd); - - // Adjust for line thickness: draw additional lines above and below - for (int t = -thickness / 2; t <= thickness / 2; ++t) { - if (steep) { - plot(yPixel1 + t, xPixel1, rfpart(yEnd) * xGap, portIndex); - plot(yPixel1 + 1 + t, xPixel1, fpart(yEnd) * xGap, portIndex); - } else { - plot(xPixel1, yPixel1 + t, rfpart(yEnd) * xGap, portIndex); - plot(xPixel1, yPixel1 + 1 + t, fpart(yEnd) * xGap, portIndex); - } - } - float intery = yEnd + gradient; - - // Handle the second endpoint - xEnd = roundFloat(x2); - yEnd = y2 + gradient * (xEnd - x2); - xGap = fpart(x2 + 0.5f); - float xPixel2 = xEnd; - float yPixel2 = ipart(yEnd); - - // Adjust for line thickness: draw additional lines above and below - for (int t = -thickness / 2; t <= thickness / 2; ++t) { - if (steep) { - plot(yPixel2 + t, xPixel2, rfpart(yEnd) * xGap, portIndex); - plot(yPixel2 + 1 + t, xPixel2, fpart(yEnd) * xGap, portIndex); - } else { - plot(xPixel2, yPixel2 + t, rfpart(yEnd) * xGap, portIndex); - plot(xPixel2, yPixel2 + 1 + t, fpart(yEnd) * xGap, portIndex); - } - } - // Main loop for drawing the line - if (steep) { - for (int x = static_cast(xPixel1 + 1); x < static_cast(xPixel2); ++x) { - for (int t = -thickness / 2; t <= thickness / 2; ++t) { - plot(ipart(intery) + t, x, rfpart(intery), portIndex); - plot(ipart(intery) + 1 + t, x, fpart(intery), portIndex); - } - intery += gradient; - } - } else { - for (int x = static_cast(xPixel1 + 1); x < static_cast(xPixel2); ++x) { - for (int t = -thickness / 2; t <= thickness / 2; ++t) { - plot(x, ipart(intery) + t, rfpart(intery), portIndex); - plot(x, ipart(intery) + 1 + t, fpart(intery), portIndex); - } - intery += gradient; - } - } + //Only update X once all the lines have been drawn + if(portIndex == lines - 1) + last_line_drawn += candle_width; + } - -/* -void PNGPlotter::drawLine(float x1, float y1, float x2, float y2, int portIndex) +void PNGPlotter::drawLine(int x1, int y1, int x2, int y2, RGBA& lineColor) { - //Digital Differential Analyzer: DDA - float dx = x2 - x1; - float dy = y2 - y1; - - //Calculate number of steps - float steps = std::max(std::abs(dx), std::abs(dy)); + x1 = clamp(x1, margin_left, width - margin_right); + x2 = clamp(x2, margin_left, width - margin_right); + y1 = clamp(y1, margin_top, height - margin_bottom); + y2 = clamp(y2, margin_top, height - margin_bottom); - //Calculate the increment per step - float xIncrement = dx / steps; - float yIncrement = dy / steps; + //Bresenham's Line algorithm (or close to it) + int dx = std::abs(x2 - x1); + int dy = std::abs(y2 - y1); - float currentX = x1; - float currentY = y1; + bool steep = dy > dx; + + //Swap if the line is steep (more vertical than horizontal - for(int i = 0; i <= steps; ++i) + if(steep) { - //Draw pixel - image.SetPixel(roundFloat(currentX), roundFloat(currentY), line_colors[portIndex]); + //swap x1 and y1 + int temp = x1; + x1 = y1; + y1 = temp; - //move pos - currentX += xIncrement; - currentY += yIncrement; - } + //Swap x2 and y2 + temp = x2; + x2 = y2; + y2 = temp; + dx = std::abs(x2 - x1); + dy = std::abs(y2 - y1); + } + int sx = x1 < x2 ? 1 : -1; + int sy = y1 < y2 ? 1 : -1; + + double gradient = static_cast(dy) / static_cast(dx); + double err = 0.0; - //Bresenham's Line algorithm (or close to it) - float dx = std::abs(x2 - x1); - float dy = std::abs(y2 - y1); - float sx = x1 < x2 ? 1 : -1; - float sy = y1 < y2 ? 1 : -1; - float err = dx - dy; + int lineWidth = 20; while (true) { - //Set pixel at (x1, y1) - image.SetPixel(x1, y1, line_colors[portIndex]); //Fix draw to get from vector + + if (steep) + { + image.SetPixel(y1, x1, lineColor); + } + else + { + image.SetPixel(x1, y1, lineColor); + } + + for(int i = 0; i <= lineWidth; ++i) + { + if (steep) { + if (y1 + i < static_cast(height) - margin_bottom) + { + image.SetPixel(y1 + i, x1, lineColor); // Anti-aliased pixel above + } + if (y1 - i >= margin_top) + { + image.SetPixel(y1 - i, x1, lineColor); // Anti-aliased pixel below + } + } + else + { + if (x1 + i < static_cast(width) - margin_right) + { + image.SetPixel(x1 + i, y1, lineColor); // Anti-aliased pixel right + } + if (x1 - i >= margin_left) + { + image.SetPixel(x1 - i, y1, lineColor); // Anti-aliased pixel left + } + } + } //Check if the end has been reached if (x1 == x2 && y1 == y2) break; - float e2 = err * 2; - if(e2 > -dy) + err += gradient; + if(err >= 0.5) { - err -= dy; - x1 += sx; + y1 += sy; + err -= 1.0; } - if (e2 < dx) - { - err += dx; - y1 += sy; - } - + x1 += sx; } -}*/ +} -void PNGPlotter::drawNewCandle(long timestamp, float raw_open, float raw_close, float raw_high, float raw_low) -{ - - float x = last_candle_pos + candle_width; +void PNGPlotter::drawNewCandle(long timestamp, float raw_open, float raw_close, float raw_high, float raw_low) { + // Adjust prices by subtracting min_price for normalization + float adjusted_open = raw_open - min_price; + float adjusted_close = raw_close - min_price; + float adjusted_high = raw_high - min_price; + float adjusted_low = raw_low - min_price; + float adjusted_max = max_price - min_price; + + // Calculate y-coordinates with adjusted prices + int y_open = height - margin_bottom - static_cast(adjusted_open / adjusted_max * (height - margin_top - margin_bottom)); + int y_close = height - margin_bottom - static_cast(adjusted_close / adjusted_max * (height - margin_top - margin_bottom)); + int y_high = height - margin_bottom - static_cast(adjusted_high / adjusted_max * (height - margin_top - margin_bottom)); + int y_low = height - margin_bottom - static_cast(adjusted_low / adjusted_max * (height - margin_top - margin_bottom)); + + y_open = clamp(y_open, margin_top, height - margin_bottom); + y_close = clamp(y_close, margin_top, height - margin_bottom); + y_high = clamp(y_high, margin_top, height - margin_bottom); + y_low = clamp(y_low, margin_top, height - margin_bottom); + + // Determine the color based on whether the candlestick is bullish or bearish + RGBA& color = (raw_close >= raw_open) ? color_bullish : color_bearish; - float y_open = height - ((raw_open - min_price) / (max_price - min_price) * (height - 2 * margin_y))- margin_y; - - float y_close = height - ((raw_close - min_price) / (max_price - min_price) * (height - 2 * margin_y)) - margin_y; + // Draw the candlestick at the calculated x position + drawCandleStick(image, last_candle_pos + margin_left, y_open, y_close, y_high, y_low, color); - float y_high = height - ((raw_high - min_price) / (max_price - min_price) * (height - 2 * margin_y)) - margin_y; + // Update the last drawn x position + last_candle_pos += candle_width; +} - float y_low = height - ((raw_low - min_price) / (max_price - min_price) * (height - 2 * margin_y)) - margin_y; +void PNGPlotter::drawCandleStick(Image& img, int x, int y_open, int y_close, int y_high, int y_low, RGBA& color) { + const int body_width = static_cast(candle_width); // Ensure width is an integer + const int half_body_width = body_width / 2; + const int wick_thickness = 20; - //Determine the color based on whether the candlestick is bullish or bearish - RGBA& color = (raw_close >= raw_open) ? color_bullish : color_bearish; + // Draw wick (line between high and low) + int wick_top = std::min(y_low, y_high); + int wick_bottom = std::max(y_low, y_high); + for (int y = wick_top; y <= wick_bottom; ++y) { + for(int i = -wick_thickness; i <= wick_thickness; ++i) + { + if (x + i >= margin_left && x + i < static_cast(img.getWidth()) - margin_right && y >= margin_top && y < static_cast(img.getHeight()) - margin_bottom) + { + img.SetPixel(x + i, y, color); + } + } + } - //Draw the candlestick at the calculated x position - drawCandleStick(image, x, y_open, y_close, y_high, y_low, color); - - //Update the last drawn x position - last_candle_pos = x; + // Draw Body (rectangle between open and close) + int body_top = std::min(y_open, y_close); + int body_bottom = std::max(y_open, y_close); + for (int y = body_top; y <= body_bottom; ++y) { + for (int dx = -half_body_width; dx <= half_body_width; ++dx) { + if (x + dx >= margin_left && x + dx < static_cast(img.getWidth()) - margin_right && y >= margin_top && y < static_cast(img.getHeight()) - margin_bottom) { + img.SetPixel(x + dx, y, color); + } + } + } } +/* void PNGPlotter::drawCandleStick(Image& img, float x, float y_open, float y_close, float y_high, float y_low, RGBA& color) { //Define the width of the candlestick body and the wick @@ -386,14 +355,14 @@ void PNGPlotter::drawCandleStick(Image& img, float x, float y_open, float y_clos } } } - +*/ Image PNGPlotter::downsampleToTargetSize() { Image downsampledImage; downsampledImage.Allocate(TARGET_WIDTH, TARGET_HEIGHT); // Calculate the size of each block of high-res pixels that corresponds to one low-res pixel - float scaleX = static_cast(SUPERSAMPLE_WIDTH) / TARGET_WIDTH; - float scaleY = static_cast(SUPERSAMPLE_HEIGHT) / TARGET_HEIGHT; + float scaleX = static_cast(width) / TARGET_WIDTH; + float scaleY = static_cast(height) / TARGET_HEIGHT; for (unsigned int y = 0; y < TARGET_HEIGHT; ++y) { for (unsigned int x = 0; x < TARGET_WIDTH; ++x) { @@ -419,6 +388,6 @@ void PNGPlotter::SavePNG(const std::string& filename, const std::string& folder) std::string full_path = folder; full_path.append("/"); full_path.append(filename); - //image.SavePNG(full_path.c_str()); +// image.SavePNG(full_path.c_str()); downsampleImage.SavePNG(full_path.c_str()); } diff --git a/Backend/Database/PNGPlotter.h b/Backend/Database/PNGPlotter.h index a522a9e..563d40c 100644 --- a/Backend/Database/PNGPlotter.h +++ b/Backend/Database/PNGPlotter.h @@ -6,38 +6,36 @@ #include #include #include +#include #include #include +#include namespace shmea{ class PNGPlotter { - struct RGB - { - int r; - int g; - int b; - }; private: Image image; - unsigned width; - unsigned height; + unsigned int width; + unsigned int height; float min_price, max_price; - float old_min_price, old_max_price; - float last_candle_pos; + const int margin_top; + const int margin_right; + const int margin_bottom; + const int margin_left; long last_timestamp; int total_candles_drawn; const int max_candles; - const float margin_x; - const float margin_y; - float candle_width; + int candle_width; + int last_candle_pos; int lines; std::vector first_line_point; - std::vector last_price_pos; - float last_line_drawn; + std::vector last_price_pos; + int last_line_drawn; std::vector line_colors; + std::map indicatorColors; std::vector line_color_names; RGBA color_bullish; RGBA color_bearish; @@ -47,10 +45,11 @@ class PNGPlotter // void generateUniqueColors(int); void initialize_colors(std::vector&, std::vector&); Image downsampleToTargetSize(); + + void drawGrid(int = 7, int = 16); - void drawLine(float, float, float, float, int); - void plot(float, float, float, int); - void drawCandleStick(Image&, float, float, float, float, float, RGBA&); + void drawLine(int, int, int, int, RGBA&); + void drawCandleStick(Image&, int, int, int, int, int, RGBA&); public: @@ -61,8 +60,9 @@ class PNGPlotter static const int SUPERSAMPLE_WIDTH = TARGET_WIDTH * SUPERSAMPLE_SCALE; static const int SUPERSAMPLE_HEIGHT = TARGET_HEIGHT * SUPERSAMPLE_SCALE; - PNGPlotter(unsigned, unsigned, int, double, double, int = 0); - void addDataPoint(double, int = 0, bool = true); + PNGPlotter(unsigned int, unsigned int, int, double, double, int = 0, int=0, int=0, int=0, int=0); + void addDataPointWithIndicator(double, int = 0, std::string = "", std::string = ""); + void addDataPoint(double, int = 0, bool = true, RGBA* = NULL); void drawNewCandle(long, float, float, float, float); void SavePNG(const std::string&, const std::string&); diff --git a/Backend/Database/image.cpp b/Backend/Database/image.cpp index d33336b..764319e 100644 --- a/Backend/Database/image.cpp +++ b/Backend/Database/image.cpp @@ -21,6 +21,65 @@ using namespace shmea; +void Image::drawVerticalGradient(int x, int y, RGBA color1, RGBA color2, int cornerRadius) +{ + int cHeight = static_cast(height); + int cWidth = static_cast(width); + + int startY = y; + int endY = y + cHeight; + + // Calculate color deltas with type casting to float for precision + float deltaR = (static_cast(color2.r) - static_cast(color1.r)) / cHeight; + float deltaG = (static_cast(color2.g) - static_cast(color1.g)) / cHeight; + float deltaB = (static_cast(color2.b) - static_cast(color1.b)) / cHeight; + float deltaA = (static_cast(color2.a) - static_cast(color1.a)) / cHeight; + + int rectXW = x + cWidth - 1; + int rectYH = y + cHeight - 1; + + // Draw the vertical gradient with adjustable rounded corners + for (int currentY = startY; currentY < endY; ++currentY) { + // Calculate current color and cast to unsigned char + unsigned char r = static_cast(color1.r + deltaR * (currentY - startY)); + unsigned char g = static_cast(color1.g + deltaG * (currentY - startY)); + unsigned char b = static_cast(color1.b + deltaB * (currentY - startY)); + unsigned char a = static_cast(color1.a + deltaA * (currentY - startY)); + + // Iterate over the width of the rectangle + for (int currentX = x; currentX <= rectXW; ++currentX) { + // Check if the pixel is within the rounded corner area + bool drawPixel = true; + int dx = 0, dy = 0; + + if (currentX < x + cornerRadius) { + dx = currentX - (x + cornerRadius); + if (currentY < y + cornerRadius) + dy = currentY - (y + cornerRadius); + else if (currentY >= rectYH - cornerRadius) + dy = currentY - (rectYH - cornerRadius); + } else if (currentX >= rectXW - cornerRadius) { + dx = currentX - (rectXW - cornerRadius); + if (currentY < y + cornerRadius) + dy = currentY - (y + cornerRadius); + else if (currentY >= rectYH - cornerRadius) + dy = currentY - (rectYH - cornerRadius); + } else if (currentY < y + cornerRadius || currentY >= rectYH - cornerRadius) { + if (currentX < x || currentX >= rectXW) + drawPixel = false; + } + + // Draw the pixel if it's not excluded by the corner radius + if (drawPixel) { + int radiusSquared = cornerRadius * cornerRadius; + if ((dx * dx + dy * dy) <= radiusSquared) { + SetPixel(currentX, currentY, RGBA(r, g, b, a)); + } + } + } + } +} + RGBA Image::averageColor(int startX, int startY, int blockWidth, int blockHeight) { int totalR = 0, totalG = 0, totalB = 0, totalA = 0; diff --git a/Backend/Database/image.h b/Backend/Database/image.h index 3241f05..cfed4d3 100644 --- a/Backend/Database/image.h +++ b/Backend/Database/image.h @@ -134,6 +134,8 @@ class Image std::vector getPixels() const; + void drawVerticalGradient(int, int, RGBA, RGBA, int); + RGBA averageColor(int, int, int, int); RGBA GetPixel(unsigned int x, unsigned int y) const diff --git a/Backend/Database/png-helper.cpp b/Backend/Database/png-helper.cpp index 7be6d84..62f6265 100644 --- a/Backend/Database/png-helper.cpp +++ b/Backend/Database/png-helper.cpp @@ -192,7 +192,6 @@ bool create_directories(const std::string& path) while((pos = path.find('/', pos)) != std::string::npos) { current_path = path.substr(0, pos++); - printf("paths: %s\n", current_path.c_str()); //Skip if the directory already exists if (current_path.empty()) continue; @@ -226,8 +225,6 @@ void PNGHelper::SavePNG(const Image& image, const char* outputPath) //Extract the dir path from the full file path std::string directory = temp.substr(0, temp.find_last_of('/')+1); - printf("dir: %s\n", directory.c_str()); - //Create the directory if it doesn't exist if(!create_directories(directory.c_str())) {