| /**************************************************************************** |
| * apps/graphics/nxwidgets/src/cscaledbitmap.hxx |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <cstring> |
| #include <debug.h> |
| |
| #include <nuttx/nx/nxglib.h> |
| |
| #include "graphics/nxwidgets/cscaledbitmap.hxx" |
| |
| /**************************************************************************** |
| * Pre-Processor Definitions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Method Implementations |
| ****************************************************************************/ |
| |
| using namespace NXWidgets; |
| |
| /** |
| * Constructor. |
| * |
| * @param bitmap The bitmap structure being scaled. |
| * @newSize The new, scaled size of the image |
| */ |
| |
| CScaledBitmap::CScaledBitmap(IBitmap *bitmap, struct nxgl_size_s &newSize) |
| : m_bitmap(bitmap), m_size(newSize) |
| { |
| // xScale will be used to convert a request X position to an X position |
| // in the contained bitmap: |
| // |
| // xImage = xRequested * oldWidth / newWidth |
| // = xRequested * xScale |
| |
| m_xScale = itob16((uint32_t)m_bitmap->getWidth()) / newSize.w; |
| |
| // Similarly, yScale will be used to convert a request Y position to a Y |
| // positionin the contained bitmap: |
| // |
| // yImage = yRequested * oldHeight / newHeight |
| // = yRequested * yScale |
| |
| m_yScale = itob16((uint32_t)m_bitmap->getHeight()) / newSize.h; |
| |
| // Allocate and initialize the row cache |
| |
| size_t stride = bitmap->getStride(); |
| m_rowCache[0] = new uint8_t[stride]; |
| m_rowCache[1] = new uint8_t[stride]; |
| |
| // Read the first two rows into the cache |
| |
| m_row = m_bitmap->getWidth(); // Set to an impossible value |
| cacheRows(0); |
| } |
| |
| /** |
| * Destructor. |
| */ |
| |
| CScaledBitmap::~CScaledBitmap(void) |
| { |
| // Delete the allocated row cache memory |
| |
| if (m_rowCache[0]) |
| { |
| delete m_rowCache[0]; |
| } |
| |
| if (m_rowCache[1]) |
| { |
| delete m_rowCache[1]; |
| } |
| |
| // We are also responsible for deleting the contained IBitmap |
| |
| if (m_bitmap) |
| { |
| delete m_bitmap; |
| } |
| } |
| |
| /** |
| * Get the bitmap's color format. |
| * |
| * @return The bitmap's width. |
| */ |
| |
| const uint8_t CScaledBitmap::getColorFormat(void) const |
| { |
| return m_bitmap->getColorFormat(); |
| } |
| |
| /** |
| * Get the bitmap's color format. |
| * |
| * @return The bitmap's color format. |
| */ |
| |
| const uint8_t CScaledBitmap::getBitsPerPixel(void) const |
| { |
| return m_bitmap->getBitsPerPixel(); |
| } |
| |
| /** |
| * Get the bitmap's width (in pixels/columns). |
| * |
| * @return The bitmap's pixel depth. |
| */ |
| |
| const nxgl_coord_t CScaledBitmap::getWidth(void) const |
| { |
| return m_size.w; |
| } |
| |
| /** |
| * Get the bitmap's height (in rows). |
| * |
| * @return The bitmap's height (in rows). |
| */ |
| |
| const nxgl_coord_t CScaledBitmap::getHeight(void) const |
| { |
| return m_size.h; |
| } |
| |
| /** |
| * Get the bitmap's width (in bytes). |
| * |
| * @return The bitmap's width (in bytes). |
| */ |
| |
| const size_t CScaledBitmap::getStride(void) const |
| { |
| return (m_bitmap->getBitsPerPixel() * m_size.w + 7) / 8; |
| } |
| |
| /** |
| * Get one row from the bit map image. |
| * |
| * REVISIT: This algorithm is really intended to expand images. Hence, |
| * for example, interpolation is between row and row+1 and column and |
| * column+1 in the original, unscaled image. You would the interpolation |
| * differently if you really wanted to sub-sample well. |
| * |
| * @param x The offset into the row to get |
| * @param y The row number to get |
| * @param width The number of pixels to get from the row |
| * @param data The memory location provided by the caller |
| * in which to return the data. This should be at least |
| * (getWidth()*getBitsPerPixl() + 7)/8 bytes in length |
| * and properly aligned for the pixel color format. |
| * @param True if the run was returned successfully. |
| */ |
| |
| bool CScaledBitmap::getRun(nxgl_coord_t x, nxgl_coord_t y, |
| nxgl_coord_t width, FAR void *data) |
| { |
| #if CONFIG_NXWIDGETS_FMT == FB_FMT_RGB8_332 || CONFIG_NXWIDGETS_FMT == FB_FMT_RGB24 |
| FAR uint8_t *dest = (FAR uint8_t *)data; |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB16_565 |
| FAR uint16_t *dest = (FAR uint16_t *)data; |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB32 |
| FAR uint32_t *dest = (FAR uint32_t *)data; |
| #else |
| # error Unsupported, invalid, or undefined color format |
| #endif |
| |
| // Check ranges. Casts to unsigned int are ugly but permit one-sided comparisons |
| |
| if (((unsigned int)x >= (unsigned int)m_size.w) && |
| ((unsigned int)(x + width) > (unsigned int)m_size.w) && |
| ((unsigned int)y <= (unsigned int)m_size.h)) |
| { |
| return false; |
| } |
| |
| // Get the row number in the unscaled image corresponding to the |
| // requested y position. This must be either the exact row or the |
| // closest row just before the requested position |
| |
| b16_t row16 = y * m_yScale; |
| nxgl_coord_t row = b16toi(row16); |
| |
| // Get that row and the one after it into the row cache. We know that |
| // the pixel value that we want is one between the two rows. This |
| // may seem wasteful to read two entire rows. However, in normal usage |
| // we will be traversal each image from top-left to bottom-right in |
| // order. In that case, the caching is most efficient. |
| |
| if (!cacheRows(row)) |
| { |
| return false; |
| } |
| |
| // Now scale and copy the data from the cached row data |
| |
| for (int i = 0; i < width; i++, x++) |
| { |
| // Get the column number in the unscaled row corresponding to the |
| // requested x position. This must be either the exact column or the |
| // closest column just before the requested position |
| |
| b16_t column = x * m_xScale; |
| |
| // Get the color at the position on the first row |
| |
| struct rgbcolor_s color1; |
| if (!rowColor(m_rowCache[0], column, color1)) |
| { |
| gerr("ERROR: rowColor failed for the first row\n"); |
| return false; |
| } |
| |
| // Get the color at the position on the first row |
| |
| struct rgbcolor_s color2; |
| if (!rowColor(m_rowCache[1], column, color2)) |
| { |
| gerr("ERROR: rowColor failed for the second row\n"); |
| return false; |
| } |
| |
| // Check for transparent colors |
| |
| bool transparent1; |
| bool transparent2; |
| |
| #if CONFIG_NXWIDGETS_FMT == FB_FMT_RGB8_332 |
| uint8_t color = RGBTO8(color1.r, color1.g, color1.b); |
| transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| color = RGBTO8(color2.r, color2.g, color2.b); |
| transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB16_565 |
| uint16_t color = RGBTO16(color1.r, color1.g, color1.b); |
| transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| color = RGBTO16(color2.r, color2.g, color2.b); |
| transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB24 || CONFIG_NXWIDGETS_FMT == FB_FMT_RGB32 |
| uint32_t color = RGBTO24(color1.r, color1.g, color1.b); |
| transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| color = RGBTO24(color2.r, color2.g, color2.b); |
| transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| #else |
| # error Unsupported, invalid, or undefined color format |
| #endif |
| |
| // Is one of the colors transparent? |
| |
| struct rgbcolor_s scaledColor; |
| b16_t fraction b16frac(row16); |
| |
| if (transparent1 || transparent2) |
| { |
| // Yes.. don't interpolate within transparent regions or |
| // between transparent and opaque regions. |
| |
| // Get the color closest to the requested position |
| |
| if (fraction < b16HALF) |
| { |
| scaledColor.r = color1.r; |
| scaledColor.g = color1.g; |
| scaledColor.b = color1.b; |
| } |
| else |
| { |
| scaledColor.r = color2.r; |
| scaledColor.g = color2.g; |
| scaledColor.b = color2.b; |
| } |
| } |
| else |
| { |
| // No.. both colors are opaque |
| |
| if (!scaleColor(color1, color2, fraction, scaledColor)) |
| { |
| return false; |
| } |
| } |
| |
| // Write the interpolated data to the user buffer |
| |
| #if CONFIG_NXWIDGETS_FMT == FB_FMT_RGB8_332 |
| color = RGBTO8(scaledColor.r, scaledColor.g, scaledColor.b); |
| *dest++ = color; |
| |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB16_565 |
| color = RGBTO16(scaledColor.r, scaledColor.g, scaledColor.b); |
| *dest++ = color; |
| |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB24 |
| *dest++ = color2.b; |
| *dest++ = color2.r; |
| *dest++ = color2.g; |
| |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB32 |
| color = RGBTO24(scaledColor.r, scaledColor.g, scaledColor.b); |
| *dest++ = color; |
| |
| #else |
| # error Unsupported, invalid, or undefined color format |
| #endif |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Read two rows into the row cache |
| * |
| * @param row - The row number of the first row to cache |
| */ |
| |
| bool CScaledBitmap::cacheRows(unsigned int row) |
| { |
| nxgl_coord_t bitmapWidth = m_bitmap->getWidth(); |
| nxgl_coord_t bitmapHeight = m_bitmap->getHeight(); |
| |
| // A common case is to advance by one row. In this case, we only |
| // need to read one row |
| |
| if (row == m_row + 1) |
| { |
| // Swap rows |
| |
| FAR uint8_t *saveRow = m_rowCache[0]; |
| m_rowCache[0] = m_rowCache[1]; |
| m_rowCache[1] = saveRow; |
| |
| // Save number of the first row that we have in the cache |
| |
| m_row = row; |
| |
| // Now read the new row into the second row cache buffer |
| |
| if (++row >= (unsigned int)bitmapHeight) |
| { |
| row = bitmapHeight - 1; |
| } |
| |
| if (!m_bitmap->getRun(0, row, bitmapWidth, m_rowCache[1])) |
| { |
| gerr("ERROR: Failed to read bitmap row %d\n", row); |
| return false; |
| } |
| } |
| |
| // Do we need to read two new rows? Or do we already have the |
| // request row in the cache? |
| |
| else if (row != m_row) |
| { |
| // Read the first row into the cache |
| |
| if (row >= (unsigned int)bitmapHeight) |
| { |
| row = bitmapHeight - 1; |
| } |
| |
| if (!m_bitmap->getRun(0, row, bitmapWidth, m_rowCache[0])) |
| { |
| gerr("ERROR: Failed to read bitmap row %d\n", row); |
| return false; |
| } |
| |
| // Save number of the first row that we have in the cache |
| |
| m_row = row; |
| |
| // Read the next row into the cache |
| |
| if (++row >= (unsigned int)bitmapHeight) |
| { |
| row = bitmapHeight - 1; |
| } |
| |
| if (!m_bitmap->getRun(0, row, bitmapWidth, m_rowCache[1])) |
| { |
| gerr("ERROR: Failed to read bitmap row %d\n", row); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Given an two RGB colors and a fractional value, return the scaled |
| * value between the two colors. |
| * |
| * @param incolor1 - The first color to be used |
| * @param incolor2 - The second color to be used |
| * @param fraction - The fractional value |
| * @param outcolor - The returned, scaled color |
| */ |
| |
| bool CScaledBitmap::scaleColor(FAR const struct rgbcolor_s &incolor1, |
| FAR const struct rgbcolor_s &incolor2, |
| b16_t fraction, FAR struct rgbcolor_s &outcolor) |
| { |
| uint32_t component; |
| b16_t red; |
| b16_t green; |
| b16_t blue; |
| |
| // A fraction of < 0.5 would mean to use use mostly color1; a fraction |
| // greater than 0.5 would men to use mostly color2 |
| |
| b16_t remainder = b16ONE - fraction; |
| |
| // Interpolate each color value (converting to b15) |
| |
| red = (b16_t)incolor1.r * remainder + (b16_t)incolor2.r * fraction; |
| green = (b16_t)incolor1.g * remainder + (b16_t)incolor2.g * fraction; |
| blue = (b16_t)incolor1.b * remainder + (b16_t)incolor2.b * fraction; |
| |
| // Return the integer, interpolated values, clipping to the range of |
| // uint8_t |
| |
| component = b16toi(red); |
| outcolor.r = component < 256 ? component : 255; |
| |
| component = b16toi(green); |
| outcolor.g = component < 256 ? component : 255; |
| |
| component = b16toi(blue); |
| outcolor.b = component < 256 ? component : 255; |
| return true; |
| } |
| |
| /** |
| * Given an image row and a non-integer column offset, return the |
| * interpolated RGB color value corresponding to that position |
| * |
| * @param row - The pointer to the row in the row cache to use |
| * @param column - The non-integer column offset |
| * @param outcolor - The returned, interpolated color |
| * |
| */ |
| |
| bool CScaledBitmap::rowColor(FAR uint8_t *row, b16_t column, |
| FAR struct rgbcolor_s &outcolor) |
| { |
| // This is the col at or just before the pixel of interest |
| |
| nxgl_coord_t col1 = b16toi(column); |
| nxgl_coord_t col2 = col1 + 1; |
| |
| nxgl_coord_t bitmapWidth = m_bitmap->getWidth(); |
| if (col2 >= bitmapWidth) |
| { |
| col2 = bitmapWidth - 1; |
| } |
| |
| b16_t fraction = b16frac(column); |
| |
| struct rgbcolor_s color1; |
| struct rgbcolor_s color2; |
| |
| bool transparent1; |
| bool transparent2; |
| |
| #if CONFIG_NXWIDGETS_FMT == FB_FMT_RGB8_332 |
| uint8_t color = row[col1]; |
| color1.r = RGB8RED(color); |
| color1.g = RGB8GREEN(color); |
| color1.b = RGB8BLUE(color); |
| |
| transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| color = row[col2]; |
| color2.r = RGB8RED(color); |
| color2.g = RGB8GREEN(color); |
| color2.b = RGB8BLUE(color); |
| |
| transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB16_565 |
| FAR uint16_t *row16 = (FAR uint16_t*)row; |
| uint16_t color = row16[col1]; |
| color1.r = RGB16RED(color); |
| color1.g = RGB16GREEN(color); |
| color1.b = RGB16BLUE(color); |
| |
| transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| color = row16[col2]; |
| color2.r = RGB16RED(color); |
| color2.g = RGB16GREEN(color); |
| color2.b = RGB16BLUE(color); |
| |
| transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB24 |
| unsigned int ndx = 3*col1; |
| color1.r = row[ndx+2]; |
| color1.g = row[ndx+1]; |
| color1.b = row[ndx]; |
| |
| uint32_t color = RGBTO24(color1.r, color1.g, color1.b); |
| transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| ndx = 3*col2; |
| color2.r = row[ndx+2]; |
| color2.g = row[ndx+1]; |
| color2.b = row[ndx]; |
| |
| color = RGBTO24(color2.r, color2.g, color2.b); |
| transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| #elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB32 |
| FAR uint32_t *row32 = (FAR uint32_t*)row; |
| uint32_t color = row32[col1]; |
| color1.r = RGB24RED(color); |
| color1.g = RGB24GREEN(color); |
| color1.b = RGB24BLUE(color); |
| |
| transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| color = row32[col2]; |
| color2.r = RGB24RED(color); |
| color2.g = RGB24GREEN(color); |
| color2.b = RGB24BLUE(color); |
| |
| transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR); |
| |
| #else |
| # error Unsupported, invalid, or undefined color format |
| #endif |
| |
| // Is one of the colors transparent? |
| |
| if (transparent1 || transparent2) |
| { |
| // Yes.. don't interpolate within transparent regions or |
| // between transparent and opaque regions. |
| |
| // Return the color closest to the requested position |
| // |
| // A fraction of < 0.5 would mean to use use mostly color1; a fraction |
| // greater than 0.5 would men to use mostly color2 |
| |
| if (fraction < b16HALF) |
| { |
| outcolor.r = color1.r; |
| outcolor.g = color1.b; |
| outcolor.g = color1.g; |
| } |
| else |
| { |
| outcolor.r = color2.r; |
| outcolor.g = color2.b; |
| outcolor.g = color2.g; |
| } |
| |
| return true; |
| } |
| else |
| { |
| // No.. both colors are opaque |
| |
| return scaleColor(color1, color2, fraction, outcolor); |
| } |
| } |