colobot/src/common/image.cpp

336 lines
8.5 KiB
C++

// * This file is part of the COLOBOT source code
// * Copyright (C) 2012, Polish Portal of Colobot (PPC)
// *
// * This program is free software: you can redistribute it and/or modify
// * it under the terms of the GNU General Public License as published by
// * the Free Software Foundation, either version 3 of the License, or
// * (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see http://www.gnu.org/licenses/.
// image.cpp
#include "image.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <png.h>
/* <---------------------------------------------------------------> */
/* The following code is from savesurf program by Angelo "Encelo" Theodorou
Source: http://encelo.netsons.org/old/sdl/
The code was refactored and modified slightly to fit the needs.
The copyright information below is kept unchanged. */
/* SaveSurf: an example on how to save a SDLSurface in PNG
Copyright (C) 2006 Angelo "Encelo" Theodorou
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
NOTE:
This program is part of "Mars, Land of No Mercy" SDL examples,
you can find other examples on http://marsnomercy.org
*/
std::string PNG_ERROR = "";
void PNGUserError(png_structp ctx, png_const_charp str)
{
PNG_ERROR = std::string(str);
}
int PNGColortypeFromSurface(SDL_Surface *surface)
{
int colortype = PNG_COLOR_MASK_COLOR; /* grayscale not supported */
if (surface->format->palette)
colortype |= PNG_COLOR_MASK_PALETTE;
else if (surface->format->Amask)
colortype |= PNG_COLOR_MASK_ALPHA;
return colortype;
}
bool PNGSaveSurface(const char *filename, SDL_Surface *surf)
{
FILE *fp;
png_structp png_ptr;
png_infop info_ptr;
int i, colortype;
png_bytep *row_pointers;
PNG_ERROR = "";
/* Opening output file */
fp = fopen(filename, "wb");
if (fp == nullptr)
{
PNG_ERROR = std::string("Could not open file '") + std::string(filename) + std::string("' for saving");
return false;
}
/* Initializing png structures and callbacks */
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, PNGUserError, nullptr);
if (png_ptr == nullptr)
return false;
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr)
{
png_destroy_write_struct(&png_ptr, static_cast<png_infopp>(nullptr));
PNG_ERROR = "png_create_info_struct() error!";
return false;
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
return false;
}
png_init_io(png_ptr, fp);
colortype = PNGColortypeFromSurface(surf);
png_set_IHDR(png_ptr, info_ptr, surf->w, surf->h, 8, colortype, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
/* Writing the image */
png_write_info(png_ptr, info_ptr);
png_set_packing(png_ptr);
row_pointers = static_cast<png_bytep*>( malloc(sizeof(png_bytep)*surf->h) );
for (i = 0; i < surf->h; i++)
row_pointers[i] = static_cast<png_bytep>( static_cast<Uint8 *>(surf->pixels) ) + i*surf->pitch;
png_write_image(png_ptr, row_pointers);
png_write_end(png_ptr, info_ptr);
/* Cleaning out... */
free(row_pointers);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
return true;
}
/* <---------------------------------------------------------------> */
CImage::CImage()
{
m_data = nullptr;
}
CImage::~CImage()
{
Free();
}
bool CImage::IsEmpty() const
{
return m_data == nullptr;
}
void CImage::Free()
{
if (m_data != nullptr)
{
if (m_data->surface != nullptr)
{
SDL_FreeSurface(m_data->surface);
m_data->surface = nullptr;
}
delete m_data;
m_data = nullptr;
}
}
ImageData* CImage::GetData()
{
return m_data;
}
Math::IntPoint CImage::GetSize() const
{
if (m_data == nullptr)
return Math::IntPoint();
return Math::IntPoint(m_data->surface->w, m_data->surface->h);
}
/**
* Image must be valid and pixel coords in valid range.
*
* \param pixel pixel coords (range x: 0..width-1 y: 0..height-1)
* \returns color
*/
Gfx::Color CImage::GetPixel(Math::IntPoint pixel)
{
assert(m_data != nullptr);
assert(pixel.x >= 0 || pixel.x <= m_data->surface->w);
assert(pixel.y >= 0 || pixel.y <= m_data->surface->h);
int bpp = m_data->surface->format->BytesPerPixel;
int index = pixel.y * m_data->surface->pitch + pixel.x * bpp;
Uint8* p = &static_cast<Uint8*>(m_data->surface->pixels)[index];
Uint32 u = 0;
switch (bpp)
{
case 1:
u = *p;
break;
case 2:
u = *reinterpret_cast<Uint16*>(p);
break;
case 3:
if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
u = (p[0] << 16) | (p[1] << 8) | p[2];
else
u = p[0] | (p[1] << 8) | (p[2] << 16);
break;
case 4:
u = *reinterpret_cast<Uint32*>(p);
break;
default:
assert(false);
}
Uint8 r = 0, g = 0, b = 0, a = 0;
SDL_GetRGBA(u, m_data->surface->format, &r, &g, &b, &a);
return Gfx::Color(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
}
/**
* Image must be valid and pixel coords in valid range.
*
* \param pixel pixel coords (range x: 0..width-1 y: 0..height-1)
* \param color color
*/
void CImage::SetPixel(Math::IntPoint pixel, Gfx::Color color)
{
assert(m_data != nullptr);
assert(pixel.x >= 0 || pixel.x <= m_data->surface->w);
assert(pixel.y >= 0 || pixel.y <= m_data->surface->h);
int bpp = m_data->surface->format->BytesPerPixel;
int index = pixel.y * m_data->surface->pitch + pixel.x * bpp;
Uint8* p = &static_cast<Uint8*>(m_data->surface->pixels)[index];
Uint8 r = static_cast<Uint8>(color.r * 255.0f);
Uint8 g = static_cast<Uint8>(color.g * 255.0f);
Uint8 b = static_cast<Uint8>(color.b * 255.0f);
Uint8 a = static_cast<Uint8>(color.a * 255.0f);
Uint32 u = SDL_MapRGBA(m_data->surface->format, r, g, b, a);
switch(bpp)
{
case 1:
*p = u;
break;
case 2:
*reinterpret_cast<Uint16*>(p) = u;
break;
case 3:
if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
{
p[0] = (u >> 16) & 0xFF;
p[1] = (u >> 8) & 0xFF;
p[2] = u & 0xFF;
}
else
{
p[0] = u & 0xFF;
p[1] = (u >> 8) & 0xFF;
p[2] = (u >> 16) & 0xFF;
}
break;
case 4:
*reinterpret_cast<Uint32*>(p) = u;
break;
default:
assert(false);
}
}
std::string CImage::GetError()
{
return m_error;
}
bool CImage::Load(const std::string& fileName)
{
if (! IsEmpty() )
Free();
m_data = new ImageData();
m_error = "";
m_data->surface = IMG_Load(fileName.c_str());
if (m_data->surface == nullptr)
{
delete m_data;
m_data = nullptr;
m_error = std::string(IMG_GetError());
return false;
}
return true;
}
bool CImage::SavePNG(const std::string& fileName)
{
if (IsEmpty())
{
m_error = "Empty image!";
return false;
}
m_error = "";
if (! PNGSaveSurface(fileName.c_str(), m_data->surface) )
{
m_error = PNG_ERROR;
return false;
}
return true;
}