// File: crn_image.h // See Copyright Notice and license at the end of inc/crnlib.h #pragma once #include "crn_color.h" #include "crn_vec.h" #include "crn_pixel_format.h" #include "crn_rect.h" namespace crnlib { template class image { public: typedef color_type color_t; typedef crnlib::vector pixel_buf_t; image() : m_width(0), m_height(0), m_pitch(0), m_total(0), m_comp_flags(pixel_format_helpers::cDefaultCompFlags), m_pPixels(NULL) { } // pitch is in PIXELS, not bytes. image(uint width, uint height, uint pitch = UINT_MAX, const color_type& background = color_type::make_black(), uint flags = pixel_format_helpers::cDefaultCompFlags) : m_comp_flags(flags) { CRNLIB_ASSERT((width > 0) && (height > 0)); if (pitch == UINT_MAX) pitch = width; m_pixel_buf.resize(pitch * height); m_width = width; m_height = height; m_pitch = pitch; m_total = m_pitch * m_height; m_pPixels = &m_pixel_buf.front(); set_all(background); } // pitch is in PIXELS, not bytes. image(color_type* pPixels, uint width, uint height, uint pitch = UINT_MAX, uint flags = pixel_format_helpers::cDefaultCompFlags) { alias(pPixels, width, height, pitch, flags); } image& operator= (const image& other) { if (this == &other) return *this; if (other.m_pixel_buf.empty()) { // This doesn't look very safe - let's make a new instance. //m_pixel_buf.clear(); //m_pPixels = other.m_pPixels; const uint total_pixels = other.m_pitch * other.m_height; if ((total_pixels) && (other.m_pPixels)) { m_pixel_buf.resize(total_pixels); m_pixel_buf.insert(0, other.m_pPixels, m_pixel_buf.size()); m_pPixels = &m_pixel_buf.front(); } else { m_pixel_buf.clear(); m_pPixels = NULL; } } else { m_pixel_buf = other.m_pixel_buf; m_pPixels = &m_pixel_buf.front(); } m_width = other.m_width; m_height = other.m_height; m_pitch = other.m_pitch; m_total = other.m_total; m_comp_flags = other.m_comp_flags; return *this; } image(const image& other) : m_width(0), m_height(0), m_pitch(0), m_total(0), m_comp_flags(pixel_format_helpers::cDefaultCompFlags), m_pPixels(NULL) { *this = other; } // pitch is in PIXELS, not bytes. void alias(color_type* pPixels, uint width, uint height, uint pitch = UINT_MAX, uint flags = pixel_format_helpers::cDefaultCompFlags) { m_pixel_buf.clear(); m_pPixels = pPixels; m_width = width; m_height = height; m_pitch = (pitch == UINT_MAX) ? width : pitch; m_total = m_pitch * m_height; m_comp_flags = flags; } // pitch is in PIXELS, not bytes. bool grant_ownership(color_type* pPixels, uint width, uint height, uint pitch = UINT_MAX, uint flags = pixel_format_helpers::cDefaultCompFlags) { if (pitch == UINT_MAX) pitch = width; if ((!pPixels) || (!width) || (!height) || (pitch < width)) { CRNLIB_ASSERT(0); return false; } if (pPixels == get_ptr()) { CRNLIB_ASSERT(0); return false; } clear(); if (!m_pixel_buf.grant_ownership(pPixels, height * pitch, height * pitch)) return false; m_pPixels = pPixels; m_width = width; m_height = height; m_pitch = pitch; m_total = pitch * height; m_comp_flags = flags; return true; } void clear() { m_pPixels = NULL; m_pixel_buf.clear(); m_width = 0; m_height = 0; m_pitch = 0; m_total = 0; m_comp_flags = pixel_format_helpers::cDefaultCompFlags; } inline bool is_valid() const { return m_total > 0; } inline pixel_format_helpers::component_flags get_comp_flags() const { return static_cast(m_comp_flags); } inline void set_comp_flags(pixel_format_helpers::component_flags new_flags) { m_comp_flags = new_flags; } inline void reset_comp_flags() { m_comp_flags = pixel_format_helpers::cDefaultCompFlags; } inline bool is_component_valid(uint index) const { CRNLIB_ASSERT(index < 4U); return utils::is_flag_set(m_comp_flags, index); } inline void set_component_valid(uint index, bool state) { CRNLIB_ASSERT(index < 4U); utils::set_flag(m_comp_flags, index, state); } inline bool has_rgb() const { return is_component_valid(0) || is_component_valid(1) || is_component_valid(2); } inline bool has_alpha() const { return is_component_valid(3); } inline bool is_grayscale() const { return utils::is_bit_set(m_comp_flags, pixel_format_helpers::cCompFlagGrayscale); } inline void set_grayscale(bool state) { utils::set_bit(m_comp_flags, pixel_format_helpers::cCompFlagGrayscale, state); } void set_all(const color_type& c) { for (uint i = 0; i < m_total; i++) m_pPixels[i] = c; } void flip_x() { const uint half_width = m_width / 2; for (uint y = 0; y < m_height; y++) { for (uint x = 0; x < half_width; x++) { color_type c((*this)(x, y)); (*this)(x, y) = (*this)(m_width - 1 - x, y); (*this)(m_width - 1 - x, y) = c; } } } void flip_y() { const uint half_height = m_height / 2; for (uint y = 0; y < half_height; y++) { for (uint x = 0; x < m_width; x++) { color_type c((*this)(x, y)); (*this)(x, y) = (*this)(x, m_height - 1 - y); (*this)(x, m_height - 1 - y) = c; } } } void convert_to_grayscale() { for (uint y = 0; y < m_height; y++) for (uint x = 0; x < m_width; x++) { color_type c((*this)(x, y)); typename color_type::component_t l = static_cast< typename color_type::component_t >(c.get_luma()); c.r = l; c.g = l; c.b = l; (*this)(x, y) = c; } set_grayscale(true); } void swizzle(uint r, uint g, uint b, uint a) { for (uint y = 0; y < m_height; y++) for (uint x = 0; x < m_width; x++) { const color_type& c = (*this)(x, y); (*this)(x, y) = color_type(c[r], c[g], c[b], c[a]); } } void set_alpha_to_luma() { for (uint y = 0; y < m_height; y++) for (uint x = 0; x < m_width; x++) { color_type c((*this)(x, y)); typename color_type::component_t l = static_cast< typename color_type::component_t >(c.get_luma()); c.a = l; (*this)(x, y) = c; } set_component_valid(3, true); } bool extract_block(color_type* pDst, uint x, uint y, uint w, uint h, bool flip_xy = false) const { if ((x >= m_width) || (y >= m_height)) { CRNLIB_ASSERT(0); return false; } if (flip_xy) { for (uint y_ofs = 0; y_ofs < h; y_ofs++) for (uint x_ofs = 0; x_ofs < w; x_ofs++) pDst[x_ofs * h + y_ofs] = get_clamped(x_ofs + x, y_ofs + y); // 5/4/12 - this was incorrectly x_ofs * 4 } else if (((x + w) > m_width) || ((y + h) > m_height)) { for (uint y_ofs = 0; y_ofs < h; y_ofs++) for (uint x_ofs = 0; x_ofs < w; x_ofs++) *pDst++ = get_clamped(x_ofs + x, y_ofs + y); } else { const color_type* pSrc = get_scanline(y) + x; for (uint i = h; i; i--) { memcpy(pDst, pSrc, w * sizeof(color_type)); pDst += w; pSrc += m_pitch; } } return true; } // No clipping! void unclipped_fill_box(uint x, uint y, uint w, uint h, const color_type& c) { if (((x + w) > m_width) || ((y + h) > m_height)) { CRNLIB_ASSERT(0); return; } color_type* p = get_scanline(y) + x; for (uint i = h; i; i--) { color_type* q = p; for (uint j = w; j; j--) *q++ = c; p += m_pitch; } } void draw_rect(int x, int y, uint width, uint height, const color_type& c) { draw_line(x, y, x + width - 1, y, c); draw_line(x, y, x, y + height - 1, c); draw_line(x + width - 1, y, x + width - 1, y + height - 1, c); draw_line(x, y + height - 1, x + width - 1, y + height - 1, c); } // No clipping! bool unclipped_blit(uint src_x, uint src_y, uint src_w, uint src_h, uint dst_x, uint dst_y, const image& src) { if ((!is_valid()) || (!src.is_valid())) { CRNLIB_ASSERT(0); return false; } if ( ((src_x + src_w) > src.get_width()) || ((src_y + src_h) > src.get_height()) ) { CRNLIB_ASSERT(0); return false; } if ( ((dst_x + src_w) > get_width()) || ((dst_y + src_h) > get_height()) ) { CRNLIB_ASSERT(0); return false; } const color_type* pS = &src(src_x, src_y); color_type* pD = &(*this)(dst_x, dst_y); const uint bytes_to_copy = src_w * sizeof(color_type); for (uint i = src_h; i; i--) { memcpy(pD, pS, bytes_to_copy); pS += src.get_pitch(); pD += get_pitch(); } return true; } // With clipping. bool blit(int dst_x, int dst_y, const image& src) { if ((!is_valid()) || (!src.is_valid())) { CRNLIB_ASSERT(0); return false; } int src_x = 0; int src_y = 0; if (dst_x < 0) { src_x = -dst_x; if (src_x >= static_cast(src.get_width())) return false; dst_x = 0; } if (dst_y < 0) { src_y = -dst_y; if (src_y >= static_cast(src.get_height())) return false; dst_y = 0; } if ((dst_x >= (int)m_width) || (dst_y >= (int)m_height)) return false; uint width = math::minimum(m_width - dst_x, src.get_width() - src_x); uint height = math::minimum(m_height - dst_y, src.get_height() - src_y); bool success = unclipped_blit(src_x, src_y, width, height, dst_x, dst_y, src); success; CRNLIB_ASSERT(success); return true; } // With clipping. bool blit(int src_x, int src_y, int src_w, int src_h, int dst_x, int dst_y, const image& src) { if ((!is_valid()) || (!src.is_valid())) { CRNLIB_ASSERT(0); return false; } rect src_rect(src_x, src_y, src_x + src_w, src_y + src_h); if (!src_rect.intersect(src.get_bounds())) return false; rect dst_rect(dst_x, dst_y, dst_x + src_rect.get_width(), dst_y + src_rect.get_height()); if (!dst_rect.intersect(get_bounds())) return false; bool success = unclipped_blit( src_rect.get_left(), src_rect.get_top(), math::minimum(src_rect.get_width(), dst_rect.get_width()), math::minimum(src_rect.get_height(), dst_rect.get_height()), dst_rect.get_left(), dst_rect.get_top(), src); success; CRNLIB_ASSERT(success); return true; } // In-place resize of image dimensions (cropping). bool resize(uint new_width, uint new_height, uint new_pitch = UINT_MAX, const color_type background = color_type::make_black()) { if (new_pitch == UINT_MAX) new_pitch = new_width; if ((new_width == m_width) && (new_height == m_height) && (new_pitch == m_pitch)) return true; if ((!new_width) || (!new_height) || (!new_pitch)) { clear(); return false; } pixel_buf_t existing_pixels; existing_pixels.swap(m_pixel_buf); if (!m_pixel_buf.try_resize(new_height * new_pitch)) { clear(); return false; } for (uint y = 0; y < new_height; y++) { for (uint x = 0; x < new_width; x++) { if ((x < m_width) && (y < m_height)) m_pixel_buf[x + y * new_pitch] = existing_pixels[x + y * m_pitch]; else m_pixel_buf[x + y * new_pitch] = background; } } m_width = new_width; m_height = new_height; m_pitch = new_pitch; m_total = new_pitch * new_height; m_pPixels = &m_pixel_buf.front(); return true; } inline uint get_width() const { return m_width; } inline uint get_height() const { return m_height; } inline uint get_total_pixels() const { return m_width * m_height; } inline rect get_bounds() const { return rect(0, 0, m_width, m_height); } inline uint get_pitch() const { return m_pitch; } inline uint get_pitch_in_bytes() const { return m_pitch * sizeof(color_type); } // Returns pitch * height, NOT width * height! inline uint get_total() const { return m_total; } inline uint get_block_width(uint block_size) const { return (m_width + block_size - 1) / block_size; } inline uint get_block_height(uint block_size) const { return (m_height + block_size - 1) / block_size; } inline uint get_total_blocks(uint block_size) const { return get_block_width(block_size) * get_block_height(block_size); } inline uint get_size_in_bytes() const { return sizeof(color_type) * m_total; } inline const color_type* get_pixels() const { return m_pPixels; } inline color_type* get_pixels() { return m_pPixels; } inline const color_type& operator() (uint x, uint y) const { CRNLIB_ASSERT((x < m_width) && (y < m_height)); return m_pPixels[x + y * m_pitch]; } inline color_type& operator() (uint x, uint y) { CRNLIB_ASSERT((x < m_width) && (y < m_height)); return m_pPixels[x + y * m_pitch]; } inline const color_type& get_unclamped(uint x, uint y) const { CRNLIB_ASSERT((x < m_width) && (y < m_height)); return m_pPixels[x + y * m_pitch]; } inline const color_type& get_clamped(int x, int y) const { x = math::clamp(x, 0, m_width - 1); y = math::clamp(y, 0, m_height - 1); return m_pPixels[x + y * m_pitch]; } // Sample image with bilinear filtering. // (x,y) - Continuous coordinates, where pixel centers are at (.5,.5), valid image coords are [0,width] and [0,height]. void get_filtered(float x, float y, color_type& result) const { x -= .5f; y -= .5f; int ix = (int)floor(x); int iy = (int)floor(y); float wx = x - ix; float wy = y - iy; color_type a(get_clamped(ix, iy)); color_type b(get_clamped(ix + 1, iy)); color_type c(get_clamped(ix, iy + 1)); color_type d(get_clamped(ix + 1, iy + 1)); for (uint i = 0; i < 4; i++) { double top = math::lerp(a[i], b[i], wx); double bot = math::lerp(c[i], d[i], wx); double m = math::lerp(top, bot, wy); if (!color_type::component_traits::cFloat) m += .5f; result.set_component(i, static_cast< typename color_type::parameter_t >(m)); } } void get_filtered(float x, float y, vec4F& result) const { x -= .5f; y -= .5f; int ix = (int)floor(x); int iy = (int)floor(y); float wx = x - ix; float wy = y - iy; color_type a(get_clamped(ix, iy)); color_type b(get_clamped(ix + 1, iy)); color_type c(get_clamped(ix, iy + 1)); color_type d(get_clamped(ix + 1, iy + 1)); for (uint i = 0; i < 4; i++) { float top = math::lerp(a[i], b[i], wx); float bot = math::lerp(c[i], d[i], wx); float m = math::lerp(top, bot, wy); result[i] = m; } } inline void set_pixel_unclipped(uint x, uint y, const color_type& c) { CRNLIB_ASSERT((x < m_width) && (y < m_height)); m_pPixels[x + y * m_pitch] = c; } inline void set_pixel_clipped(int x, int y, const color_type& c) { if ((static_cast(x) >= m_width) || (static_cast(y) >= m_height)) return; m_pPixels[x + y * m_pitch] = c; } inline const color_type* get_scanline(uint y) const { CRNLIB_ASSERT(y < m_height); return &m_pPixels[y * m_pitch]; } inline color_type* get_scanline(uint y) { CRNLIB_ASSERT(y < m_height); return &m_pPixels[y * m_pitch]; } inline const color_type* get_ptr() const { return m_pPixels; } inline color_type* get_ptr() { return m_pPixels; } inline void swap(image& other) { utils::swap(m_width, other.m_width); utils::swap(m_height, other.m_height); utils::swap(m_pitch, other.m_pitch); utils::swap(m_total, other.m_total); utils::swap(m_comp_flags, other.m_comp_flags); utils::swap(m_pPixels, other.m_pPixels); m_pixel_buf.swap(other.m_pixel_buf); } void draw_line(int xs, int ys, int xe, int ye, const color_type& color) { if (xs > xe) { utils::swap(xs, xe); utils::swap(ys, ye); } int dx = xe - xs, dy = ye - ys; if (!dx) { if (ys > ye) utils::swap(ys, ye); for (int i = ys ; i <= ye ; i++) set_pixel_clipped(xs, i, color); } else if (!dy) { for (int i = xs ; i < xe ; i++) set_pixel_clipped(i, ys, color); } else if (dy > 0) { if (dy <= dx) { int e = 2 * dy - dx, e_no_inc = 2 * dy, e_inc = 2 * (dy - dx); rasterize_line(xs, ys, xe, ye, 0, 1, e, e_inc, e_no_inc, color); } else { int e = 2 * dx - dy, e_no_inc = 2 * dx, e_inc = 2 * (dx - dy); rasterize_line(xs, ys, xe, ye, 1, 1, e, e_inc, e_no_inc, color); } } else { dy = -dy; if (dy <= dx) { int e = 2 * dy - dx, e_no_inc = 2 * dy, e_inc = 2 * (dy - dx); rasterize_line(xs, ys, xe, ye, 0, -1, e, e_inc, e_no_inc, color); } else { int e = 2 * dx - dy, e_no_inc = (2 * dx), e_inc = 2 * (dx - dy); rasterize_line(xe, ye, xs, ys, 1, -1, e, e_inc, e_no_inc, color); } } } const pixel_buf_t& get_pixel_buf() const { return m_pixel_buf; } pixel_buf_t& get_pixel_buf() { return m_pixel_buf; } private: uint m_width; uint m_height; uint m_pitch; uint m_total; uint m_comp_flags; color_type* m_pPixels; pixel_buf_t m_pixel_buf; void rasterize_line(int xs, int ys, int xe, int ye, int pred, int inc_dec, int e, int e_inc, int e_no_inc, const color_type& color) { int start, end, var; if (pred) { start = ys; end = ye; var = xs; for (int i = start; i <= end; i++) { set_pixel_clipped(var, i, color); if (e < 0) e += e_no_inc; else { var += inc_dec; e += e_inc; } } } else { start = xs; end = xe; var = ys; for (int i = start; i <= end; i++) { set_pixel_clipped(i, var, color); if (e < 0) e += e_no_inc; else { var += inc_dec; e += e_inc; } } } } }; typedef image image_u8; typedef image image_i16; typedef image image_u16; typedef image image_i32; typedef image image_u32; typedef image image_f; template inline void swap(image& a, image& b) { a.swap(b); } } // namespace crnlib