mirror of
https://git.code.sf.net/p/fuse-emulator/fuse
synced 2026-01-28 14:20:54 +03:00
463 lines
13 KiB
C
463 lines
13 KiB
C
/* screenshot.c: Routines for handling .png and .scr screenshots
|
|
Copyright (c) 2002-2015 Philip Kendall, Fredrick Meunier
|
|
|
|
$Id$
|
|
|
|
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.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
Author contact information:
|
|
|
|
E-mail: philip-fuse@shadowmagic.org.uk
|
|
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
|
|
#include <libspectrum.h>
|
|
|
|
#include "display.h"
|
|
#include "machine.h"
|
|
#include "peripherals/scld.h"
|
|
#include "screenshot.h"
|
|
#include "settings.h"
|
|
#include "ui/scaler/scaler.h"
|
|
#include "ui/ui.h"
|
|
#include "utils.h"
|
|
|
|
#define MONO_BITMAP_SIZE 6144
|
|
#define HICOLOUR_SCR_SIZE (2 * MONO_BITMAP_SIZE)
|
|
#define HIRES_ATTR HICOLOUR_SCR_SIZE
|
|
#define HIRES_SCR_SIZE (HICOLOUR_SCR_SIZE + 1)
|
|
|
|
#ifdef USE_LIBPNG
|
|
|
|
#include <png.h>
|
|
#ifdef HAVE_ZLIB_H
|
|
#define ZLIB_CONST
|
|
#include <zlib.h>
|
|
#endif /* #ifdef HAVE_ZLIB_H */
|
|
|
|
static int get_rgb32_data( libspectrum_byte *rgb32_data, size_t stride,
|
|
size_t height, size_t width );
|
|
static int rgb32_to_rgb24( libspectrum_byte *rgb24_data, size_t rgb24_stride,
|
|
libspectrum_byte *rgb32_data, size_t rgb32_stride,
|
|
size_t height, size_t width );
|
|
|
|
/* The biggest size screen (in units of DISPLAY_ASPECT_WIDTH x
|
|
DISPLAY_SCREEN_HEIGHT ie a Timex screen is size 2) we will be
|
|
creating via the scalers */
|
|
#define MAX_SIZE 3
|
|
|
|
/* The space used for drawing the screen image on. Out here to avoid placing
|
|
these large objects on the stack */
|
|
static libspectrum_byte
|
|
rgb_data1[ MAX_SIZE * DISPLAY_SCREEN_HEIGHT * 3 * DISPLAY_ASPECT_WIDTH * 4 ],
|
|
rgb_data2[ MAX_SIZE * DISPLAY_SCREEN_HEIGHT * 3 * DISPLAY_ASPECT_WIDTH * 4 ],
|
|
png_data[ MAX_SIZE * DISPLAY_SCREEN_HEIGHT * 3 * DISPLAY_ASPECT_WIDTH * 3 ];
|
|
|
|
int
|
|
screenshot_write( const char *filename, scaler_type scaler )
|
|
{
|
|
FILE *f;
|
|
|
|
png_structp png_ptr;
|
|
png_infop info_ptr;
|
|
|
|
libspectrum_byte *row_pointers[ MAX_SIZE * DISPLAY_SCREEN_HEIGHT ];
|
|
size_t rgb_stride = MAX_SIZE * DISPLAY_ASPECT_WIDTH * 4,
|
|
png_stride = MAX_SIZE * DISPLAY_ASPECT_WIDTH * 3;
|
|
size_t y, base_height, base_width, height, width;
|
|
int error;
|
|
|
|
if( machine_current->timex ) {
|
|
base_height = 2 * DISPLAY_SCREEN_HEIGHT;
|
|
base_width = DISPLAY_SCREEN_WIDTH;
|
|
} else {
|
|
base_height = DISPLAY_SCREEN_HEIGHT;
|
|
base_width = DISPLAY_ASPECT_WIDTH;
|
|
}
|
|
|
|
/* Change from paletted data to RGB data */
|
|
error = get_rgb32_data( rgb_data1, rgb_stride, base_height, base_width );
|
|
if( error ) return error;
|
|
|
|
/* Actually scale the data here */
|
|
scaler_get_proc32( scaler )( rgb_data1, rgb_stride, rgb_data2, rgb_stride,
|
|
base_width, base_height );
|
|
|
|
height = base_height * scaler_get_scaling_factor( scaler );
|
|
width = base_width * scaler_get_scaling_factor( scaler );
|
|
|
|
/* Reduce from RGB(padding byte) to just RGB */
|
|
error = rgb32_to_rgb24( png_data, png_stride, rgb_data2, rgb_stride,
|
|
height, width );
|
|
if( error ) return error;
|
|
|
|
for( y = 0; y < height; y++ )
|
|
row_pointers[y] = &png_data[ y * png_stride ];
|
|
|
|
f = fopen( filename, "wb" );
|
|
if( !f ) {
|
|
ui_error( UI_ERROR_ERROR, "Couldn't open `%s': %s", filename,
|
|
strerror( errno ) );
|
|
return 1;
|
|
}
|
|
|
|
png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING,
|
|
NULL, NULL, NULL );
|
|
if( !png_ptr ) {
|
|
ui_error( UI_ERROR_ERROR, "Couldn't allocate png_ptr" );
|
|
fclose( f );
|
|
return 1;
|
|
}
|
|
|
|
info_ptr = png_create_info_struct( png_ptr );
|
|
if( !info_ptr ) {
|
|
ui_error( UI_ERROR_ERROR, "Couldn't allocate info_ptr" );
|
|
png_destroy_write_struct( &png_ptr, NULL );
|
|
fclose( f );
|
|
return 1;
|
|
}
|
|
|
|
/* Set up the error handling; libpng will return to here if it
|
|
encounters an error */
|
|
if( setjmp( png_jmpbuf( png_ptr ) ) ) {
|
|
ui_error( UI_ERROR_ERROR, "Error from libpng" );
|
|
png_destroy_write_struct( &png_ptr, &info_ptr );
|
|
fclose( f );
|
|
return 1;
|
|
}
|
|
|
|
png_init_io( png_ptr, f );
|
|
|
|
/* Make files as small as possible */
|
|
png_set_compression_level( png_ptr, Z_BEST_COMPRESSION );
|
|
|
|
png_set_IHDR( png_ptr, info_ptr,
|
|
width, height, 8,
|
|
PNG_COLOR_TYPE_RGB,
|
|
PNG_INTERLACE_NONE,
|
|
PNG_COMPRESSION_TYPE_DEFAULT,
|
|
PNG_FILTER_TYPE_DEFAULT );
|
|
|
|
png_set_rows( png_ptr, info_ptr, row_pointers );
|
|
|
|
png_write_png( png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL );
|
|
|
|
png_destroy_write_struct( &png_ptr, &info_ptr );
|
|
|
|
if( fclose( f ) ) {
|
|
ui_error( UI_ERROR_ERROR, "Couldn't close `%s': %s", filename,
|
|
strerror( errno ) );
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
get_rgb32_data( libspectrum_byte *rgb32_data, size_t stride,
|
|
size_t height, size_t width )
|
|
{
|
|
size_t i, x, y;
|
|
|
|
static const /* R G B */
|
|
libspectrum_byte palette[16][3] = { { 0, 0, 0 },
|
|
{ 0, 0, 192 },
|
|
{ 192, 0, 0 },
|
|
{ 192, 0, 192 },
|
|
{ 0, 192, 0 },
|
|
{ 0, 192, 192 },
|
|
{ 192, 192, 0 },
|
|
{ 192, 192, 192 },
|
|
{ 0, 0, 0 },
|
|
{ 0, 0, 255 },
|
|
{ 255, 0, 0 },
|
|
{ 255, 0, 255 },
|
|
{ 0, 255, 0 },
|
|
{ 0, 255, 255 },
|
|
{ 255, 255, 0 },
|
|
{ 255, 255, 255 } };
|
|
|
|
libspectrum_byte grey_palette[16];
|
|
|
|
/* Addition of 0.5 is to avoid rounding errors */
|
|
for( i = 0; i < 16; i++ )
|
|
grey_palette[i] = ( 0.299 * palette[i][0] +
|
|
0.587 * palette[i][1] +
|
|
0.114 * palette[i][2] ) + 0.5;
|
|
|
|
for( y = 0; y < height; y++ ) {
|
|
for( x = 0; x < width; x++ ) {
|
|
|
|
size_t colour;
|
|
libspectrum_byte red, green, blue;
|
|
|
|
colour = display_getpixel( x, y );
|
|
|
|
if( settings_current.bw_tv ) {
|
|
|
|
red = green = blue = grey_palette[colour];
|
|
|
|
} else {
|
|
|
|
red = palette[colour][0];
|
|
green = palette[colour][1];
|
|
blue = palette[colour][2];
|
|
|
|
}
|
|
|
|
rgb32_data[ y * stride + 4 * x ] = red;
|
|
rgb32_data[ y * stride + 4 * x + 1 ] = green;
|
|
rgb32_data[ y * stride + 4 * x + 2 ] = blue;
|
|
rgb32_data[ y * stride + 4 * x + 3 ] = 0; /* padding */
|
|
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rgb32_to_rgb24( libspectrum_byte *rgb24_data, size_t rgb24_stride,
|
|
libspectrum_byte *rgb32_data, size_t rgb32_stride,
|
|
size_t height, size_t width )
|
|
{
|
|
size_t x, y;
|
|
|
|
for( y = 0; y < height; y++ ) {
|
|
for( x = 0; x < width; x++ ) {
|
|
rgb24_data[y*rgb24_stride +3*x ] = rgb32_data[y*rgb32_stride +4*x ];
|
|
rgb24_data[y*rgb24_stride +3*x +1] = rgb32_data[y*rgb32_stride +4*x +1];
|
|
rgb24_data[y*rgb24_stride +3*x +2] = rgb32_data[y*rgb32_stride +4*x +2];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
screenshot_available_scalers( scaler_type scaler )
|
|
{
|
|
if( machine_current->timex ) {
|
|
|
|
switch( scaler ) {
|
|
|
|
case SCALER_HALF: case SCALER_HALFSKIP: case SCALER_NORMAL:
|
|
case SCALER_TIMEXTV: case SCALER_PALTV:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch( scaler ) {
|
|
|
|
case SCALER_NORMAL: case SCALER_DOUBLESIZE: case SCALER_TRIPLESIZE:
|
|
case SCALER_2XSAI: case SCALER_SUPER2XSAI: case SCALER_SUPEREAGLE:
|
|
case SCALER_ADVMAME2X: case SCALER_ADVMAME3X: case SCALER_TV2X:
|
|
case SCALER_DOTMATRIX: case SCALER_PALTV2X: case SCALER_PALTV3X:
|
|
case SCALER_HQ2X: case SCALER_HQ3X:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* #ifdef USE_LIBPNG */
|
|
|
|
int
|
|
screenshot_scr_write( const char *filename )
|
|
{
|
|
libspectrum_byte scr_data[HIRES_SCR_SIZE];
|
|
int scr_length;
|
|
|
|
memset( scr_data, 0, HIRES_SCR_SIZE );
|
|
|
|
if( scld_last_dec.name.hires ) {
|
|
memcpy( scr_data,
|
|
&RAM[ memory_current_screen ][display_get_addr(0,0)],
|
|
MONO_BITMAP_SIZE );
|
|
memcpy( scr_data + MONO_BITMAP_SIZE,
|
|
&RAM[ memory_current_screen ][display_get_addr(0,0) +
|
|
ALTDFILE_OFFSET], MONO_BITMAP_SIZE );
|
|
scr_data[HIRES_ATTR] = ( scld_last_dec.byte & HIRESCOLMASK ) |
|
|
scld_last_dec.mask.scrnmode;
|
|
scr_length = HIRES_SCR_SIZE;
|
|
}
|
|
else if( scld_last_dec.name.b1 ) {
|
|
memcpy( scr_data,
|
|
&RAM[ memory_current_screen ][display_get_addr(0,0)],
|
|
MONO_BITMAP_SIZE );
|
|
memcpy( scr_data + MONO_BITMAP_SIZE,
|
|
&RAM[ memory_current_screen ][display_get_addr(0,0) +
|
|
ALTDFILE_OFFSET], MONO_BITMAP_SIZE );
|
|
scr_length = HICOLOUR_SCR_SIZE;
|
|
}
|
|
else { /* ALTDFILE and default */
|
|
memcpy( scr_data,
|
|
&RAM[ memory_current_screen ][display_get_addr(0,0)],
|
|
STANDARD_SCR_SIZE );
|
|
scr_length = STANDARD_SCR_SIZE;
|
|
}
|
|
|
|
return utils_write_file( filename, scr_data, scr_length );
|
|
}
|
|
|
|
#ifdef WORDS_BIGENDIAN
|
|
|
|
typedef struct {
|
|
unsigned b0 : 1;
|
|
unsigned b1 : 1;
|
|
unsigned b2 : 1;
|
|
unsigned b3 : 1;
|
|
unsigned b4 : 1;
|
|
unsigned b5 : 1;
|
|
unsigned b6 : 1;
|
|
unsigned b7 : 1;
|
|
} byte_field_type;
|
|
|
|
#else /* #ifdef WORDS_BIGENDIAN */
|
|
|
|
typedef struct {
|
|
unsigned b7 : 1;
|
|
unsigned b6 : 1;
|
|
unsigned b5 : 1;
|
|
unsigned b4 : 1;
|
|
unsigned b3 : 1;
|
|
unsigned b2 : 1;
|
|
unsigned b1 : 1;
|
|
unsigned b0 : 1;
|
|
} byte_field_type;
|
|
|
|
#endif /* #ifdef WORDS_BIGENDIAN */
|
|
|
|
typedef union {
|
|
libspectrum_byte byte;
|
|
byte_field_type bit;
|
|
} bft_union;
|
|
|
|
static libspectrum_byte
|
|
convert_hires_to_lores( libspectrum_byte high, libspectrum_byte low )
|
|
{
|
|
bft_union ret, h, l;
|
|
|
|
h.byte = high;
|
|
l.byte = low;
|
|
|
|
ret.bit.b0 = l.bit.b0;
|
|
ret.bit.b1 = l.bit.b2;
|
|
ret.bit.b2 = l.bit.b4;
|
|
ret.bit.b3 = l.bit.b6;
|
|
ret.bit.b4 = h.bit.b0;
|
|
ret.bit.b5 = h.bit.b2;
|
|
ret.bit.b6 = h.bit.b4;
|
|
ret.bit.b7 = h.bit.b6;
|
|
|
|
return ret.byte;
|
|
}
|
|
|
|
int
|
|
screenshot_scr_read( const char *filename )
|
|
{
|
|
int error = 0;
|
|
int i;
|
|
utils_file screen;
|
|
|
|
error = utils_read_file( filename, &screen );
|
|
if( error ) return error;
|
|
|
|
switch( screen.length ) {
|
|
case STANDARD_SCR_SIZE:
|
|
memcpy( &RAM[ memory_current_screen ][display_get_addr(0,0)],
|
|
screen.buffer, screen.length );
|
|
|
|
/* If it is a Timex and it is in hi colour or hires mode, switch out of
|
|
hires or hicolour mode */
|
|
if( scld_last_dec.name.b1 || scld_last_dec.name.hires )
|
|
scld_dec_write( 0xff, scld_last_dec.byte & ~HIRES );
|
|
break;
|
|
|
|
case HICOLOUR_SCR_SIZE:
|
|
/* If it is a Timex and it is not in hi colour mode, copy screen and switch
|
|
mode if neccesary */
|
|
/* If it is not a Timex copy the mono bitmap and raise an error */
|
|
if( machine_current->timex ) {
|
|
if( !scld_last_dec.name.b1 )
|
|
scld_dec_write( 0xff, ( scld_last_dec.byte & ~HIRESATTR ) | EXTCOLOUR );
|
|
memcpy( &RAM[ memory_current_screen ][display_get_addr(0,0) +
|
|
ALTDFILE_OFFSET], screen.buffer + MONO_BITMAP_SIZE,
|
|
MONO_BITMAP_SIZE );
|
|
} else
|
|
ui_error( UI_ERROR_INFO,
|
|
"The file contained a TC2048 high-colour screen, loaded as mono");
|
|
|
|
memcpy( &RAM[ memory_current_screen ][display_get_addr(0,0)],
|
|
screen.buffer, MONO_BITMAP_SIZE );
|
|
break;
|
|
|
|
case HIRES_SCR_SIZE:
|
|
/* If it is a Timex and it is not in hi res mode, copy screen and switch
|
|
mode if neccesary */
|
|
/* If it is not a Timex scale the bitmap and raise an error */
|
|
if( machine_current->timex ) {
|
|
memcpy( &RAM[ memory_current_screen ][display_get_addr(0,0)],
|
|
screen.buffer, MONO_BITMAP_SIZE );
|
|
|
|
memcpy( &RAM[ memory_current_screen ][display_get_addr(0,0)] +
|
|
ALTDFILE_OFFSET, screen.buffer + MONO_BITMAP_SIZE,
|
|
MONO_BITMAP_SIZE );
|
|
if( !scld_last_dec.name.hires )
|
|
scld_dec_write( 0xff,
|
|
( scld_last_dec.byte & ~( HIRESCOLMASK | HIRES ) ) |
|
|
( *(screen.buffer + HIRES_ATTR) & ( HIRESCOLMASK | HIRES ) ) );
|
|
} else {
|
|
libspectrum_byte attr = hires_convert_dec( *(screen.buffer + HIRES_ATTR) );
|
|
|
|
for( i = 0; i < MONO_BITMAP_SIZE; i++ )
|
|
RAM[ memory_current_screen ][display_get_addr(0,0) + i] =
|
|
convert_hires_to_lores( *(screen.buffer + MONO_BITMAP_SIZE + i),
|
|
*(screen.buffer + i) );
|
|
|
|
/* set attributes based on hires attribute byte */
|
|
for( i = 0; i < 768; i++ )
|
|
RAM[ memory_current_screen ][display_get_addr(0,0) +
|
|
MONO_BITMAP_SIZE + i] = attr;
|
|
|
|
ui_error( UI_ERROR_INFO,
|
|
"The file contained a TC2048 high-res screen, converted to lores");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ui_error( UI_ERROR_ERROR, "'%s' is not a valid scr file", filename );
|
|
error = 1;
|
|
}
|
|
|
|
utils_close_file( &screen );
|
|
|
|
display_refresh_all();
|
|
|
|
return error;
|
|
}
|