FastUIDraw
Example Image

Simple example showing how to use fastuidraw::PainterBrush to render an image.

The Example Image builds from Example Initialization to demonstrate how to use fastuidraw::PainterBrush to render an image.

A fastuidraw::Image requires a pixel data specified by a class derived from fastuidraw::ImageSourceBase to be realized. In this example we also implement an example of fastuidraw::ImageSourceBase that relies on SDL2_image to load the image data.

The implementation of drawing an is given by:

#include <iostream>
#include "initialization.hpp"
#include "image_loader.hpp"
class ExampleImage:public Initialization
{
public:
ExampleImage(DemoRunner *runner, int argc, char **argv);
~ExampleImage()
{}
virtual
void
draw_frame(void) override;
private:
};
ExampleImage::
ExampleImage(DemoRunner *runner, int argc, char **argv):
Initialization(runner, argc, argv)
{
/* Create the fastuidraw::Image using ImageSourceSDL to
* direct SDL2_image to do the heavy lifting of loading
* the pixel data.
*/
ImageSourceSDL image_loader(argv[1]);
m_image = m_painter_engine_gl->image_atlas().create(image_loader.width(),
image_loader.height(),
image_loader);
}
void
ExampleImage::
draw_frame(void)
{
fastuidraw::vec2 window_dims(window_dimensions());
fastuidraw::PainterSurface::Viewport vwp(0, 0, window_dims.x(), window_dims.y());
m_surface_gl->viewport(vwp);
m_painter->begin(m_surface_gl, fastuidraw::Painter::y_increases_downwards);
/* Set the fastuidraw::PainterBrush to use m_image as the
* image source. The value of color() sets by what color
* the image is modulated (in this case a value of (1, 1, 1, 1)
* indicates that the image is not modulated.
*/
brush
.color(1.0f, 1.0f, 1.0f, 1.0f)
.image(m_image);
m_painter->fill_rect(brush,
.min_point(0.0f, 0.0f)
.max_point(window_dims));
m_painter->end();
fastuidraw_glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
fastuidraw_glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
m_surface_gl->blit_surface(GL_NEAREST);
}
int
main(int argc, char **argv)
{
DemoRunner demo_runner;
if (argc < 2)
{
std::cerr << "Usage: " << argv[0] << " image_file\n";
return -1;
}
return demo_runner.main<ExampleImage>(argc, argv);
}

The interface for loading an image is given by:

#include <vector>
#include <SDL_image.h>
/* An example implementation of fastuidraw::ImageSourceBase that
* reads image data from an SDL_Surface.
*/
class ImageSourceSDL:public fastuidraw::ImageSourceBase
{
public:
/* Ctor from an SDL_Surface. After construction, the
* ImagerSourceSDL -owns- the SDL_Surface and will free
* it at its dtor.
*/
explicit
ImageSourceSDL(SDL_Surface *surface);
/* Ctor. Will load the image from the named file using SDL_image2. */
explicit
ImageSourceSDL(fastuidraw::c_string filename);
~ImageSourceSDL();
int
width(void) const;
int
height(void) const;
virtual
bool
all_same_color(fastuidraw::ivec2 location, int square_size,
fastuidraw::u8vec4 *dst) const override;
virtual
unsigned int
number_levels(void) const override;
virtual
void
fetch_texels(unsigned int level, fastuidraw::ivec2 location,
unsigned int w, unsigned int h,
virtual
format(void) const override;
private:
/* Dead simple class that stores pixels in a linear array. */
class PerMipmapLevel
{
public:
PerMipmapLevel(int w, int h):
m_pixels(w * h),
m_width(w),
m_height(h),
m_stride(w)
{}
/* use C++ move semantics to avoid potentially
* expensive copy of pixel data.
*/
PerMipmapLevel(PerMipmapLevel &&src) noexcept:
m_pixels(std::move(src.m_pixels)),
m_width(src.m_width),
m_height(src.m_height),
m_stride(src.m_stride)
{}
PerMipmapLevel(const PerMipmapLevel &) = delete;
pixel(int x, int y)
{
clamp(x, y);
return m_pixels[x + y * m_stride];
}
pixel(int x, int y) const
{
clamp(x, y);
return m_pixels[x + y * m_stride];
}
int
width(void) const
{
return m_width;
}
int
height(void) const
{
return m_height;
}
private:
void
clamp(int &x, int &y) const
{
x = fastuidraw::t_max(0, fastuidraw::t_min(x, m_width - 1));
y = fastuidraw::t_max(0, fastuidraw::t_min(y, m_height - 1));
}
std::vector<fastuidraw::u8vec4> m_pixels;
int m_width, m_height, m_stride;
};
void
extract_image_data_from_surface(SDL_Surface *surface);
std::vector<PerMipmapLevel> m_image_data;
};

The implementation for loading an image is given by:

#include <iostream>
#include "image_loader.hpp"
static
inline
GetRGBA(Uint32 pixel, const SDL_PixelFormat *fmt)
{
Uint8 r, g, b, a;
SDL_GetRGBA(pixel, fmt, &r, &g, &b, &a);
return fastuidraw::u8vec4(r, g, b, a);
}
static
inline
GetRGBA(const SDL_Surface *surface, int x, int y)
{
const unsigned char *surface_data;
Uint32 pixel, surface_offset;
surface_data = reinterpret_cast<const unsigned char*>(surface->pixels);
surface_offset = y * surface->pitch + x * surface->format->BytesPerPixel;
pixel = *reinterpret_cast<const Uint32*>(surface_data + surface_offset);
return GetRGBA(pixel, surface->format);
}
ImageSourceSDL::
ImageSourceSDL(SDL_Surface *surface)
{
extract_image_data_from_surface(surface);
}
ImageSourceSDL::
ImageSourceSDL(fastuidraw::c_string filename)
{
SDL_Surface *surface;
surface = IMG_Load(filename);
extract_image_data_from_surface(surface);
}
ImageSourceSDL::
~ImageSourceSDL()
{
}
void
ImageSourceSDL::
extract_image_data_from_surface(SDL_Surface *surface)
{
if (!surface || surface->w == 0 || surface->h == 0)
{
std::cerr << "Warning: unable to load image, substituting with an "
<< "image with width and height 1 whose only pixel is "
<< "(255, 255, 0, 255)\n";
m_image_data.push_back(PerMipmapLevel(1, 1));
m_image_data.back().pixel(0, 0) = fastuidraw::u8vec4(255, 255, 0, 255);
return;
}
/* First extract the image draw from surface, copying the pixel
* values to m_image_data[0]
*/
m_image_data.push_back(std::move(PerMipmapLevel(surface->w, surface->h)));
SDL_LockSurface(surface);
for (int y = 0; y < m_image_data.back().height(); ++y)
{
for (int x = 0; x < m_image_data.back().width(); ++x)
{
m_image_data.back().pixel(x, y) = GetRGBA(surface, x, y);
}
}
SDL_UnlockSurface(surface);
SDL_FreeSurface(surface);
/* Now generate the mipmaps iteratively by applying a simple box-filter
* to the previous level to generate the next level
*/
while (m_image_data.back().width() > 1 && m_image_data.back().height() > 1)
{
int dst_w(m_image_data.back().width() / 2);
int dst_h(m_image_data.back().height() / 2);
m_image_data.push_back(std::move(PerMipmapLevel(dst_w, dst_h)));
const PerMipmapLevel &prev(m_image_data[m_image_data.size() - 2]);
for (int y = 0; y < dst_h; ++y)
{
for (int x = 0; x < dst_w; ++x)
{
fastuidraw::vec4 p00, p10, p01, p11, avg;
int src_x(2 * x), src_y(2 * y);
p00 = fastuidraw::vec4(prev.pixel(src_x + 0, src_y + 0));
p10 = fastuidraw::vec4(prev.pixel(src_x + 1, src_y + 0));
p01 = fastuidraw::vec4(prev.pixel(src_x + 0, src_y + 1));
p11 = fastuidraw::vec4(prev.pixel(src_x + 1, src_y + 1));
avg = 0.25f * (p00 + p10 + p10 + p11);
m_image_data.back().pixel(x, y) = fastuidraw::u8vec4(avg);
}
}
}
}
int
ImageSourceSDL::
width(void) const
{
return m_image_data.front().width();
}
int
ImageSourceSDL::
height(void) const
{
return m_image_data.front().height();
}
unsigned int
ImageSourceSDL::
number_levels(void) const
{
/* fastuidraw::Image objects support mipmapping and the number
* of LOD's is specified by the fastuidraw::ImageSourceBase
* that constructs them.
*/
FASTUIDRAWassert(!m_image_data.empty());
return m_image_data.size();
}
ImageSourceSDL::
format(void) const
{
}
void
ImageSourceSDL::
fetch_texels(unsigned int level, fastuidraw::ivec2 location,
unsigned int w, unsigned int h,
{
/* This is the function that provides pixel data; in this simple
* example code, we just copy the data from m_image_data
*/
FASTUIDRAWassert(level < m_image_data.size());
for (int src_y = location.y(), dst_y = 0; dst_y < h; ++dst_y, ++src_y)
{
for (int src_x = location.x(), dst_x = 0; dst_x < w; ++dst_x, ++src_x)
{
int dst_offset;
dst_offset = dst_y * w + dst_x;
dst[dst_offset] = m_image_data[level].pixel(src_x, src_y);
}
}
}
bool
ImageSourceSDL::
all_same_color(fastuidraw::ivec2 location, int square_size,
fastuidraw::u8vec4 *dst) const
{
bool return_value(true);
/* fastuidraw::Image has an optimization where if a multiple tiles
* of an image are all the same constant color, the tile is used
* multiple times instead of being in memory multiple times; this
* call back function is what ImageAtlas uses to decide if a region
* all has all pixels as the same color value.
*/
*dst = m_image_data.front().pixel(location.x(), location.y());
for (int src_y = location.y(), dst_y = 0; return_value && dst_y < square_size; ++dst_y, ++src_y)
{
for (int src_x = location.x(), dst_x = 0; return_value && dst_x < square_size; ++dst_x, ++src_x)
{
if (*dst != m_image_data.front().pixel(src_x, src_y))
{
return_value = false;
}
}
}
return return_value;
}