/* screenshot.c: Routines for handling .png and .scr screenshots Copyright (c) 2002-2003 Philip Kendall, Fredrick Meunier $Id: screenshot.c,v 1.33.2.1 2007/05/01 13:23:51 fredm Exp $ 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 Author contact information: E-mail: philip-fuse@shadowmagic.org.uk */ #include #include #include #include #include #include "display.h" #include "machine.h" #include "screenshot.h" #include "scld.h" #include "settings.h" #include "ui/scaler/scaler.h" #include "ui/ui.h" #include "utils.h" #define STANDARD_SCR_SIZE 6912 #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 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 ]; scaler_type screenshot_movie_scaler = SCALER_NUM; static int screenshot_write2( const char *filename, scaler_type scaler, int compression ) { 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, 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; } int screenshot_write( const char *filename, scaler_type scaler ) { return screenshot_write2( filename, scaler, Z_BEST_COMPRESSION ); } int screenshot_write_fast( const char *filename, scaler_type scaler ) { return screenshot_write2( filename, scaler, Z_BEST_SPEED ); } 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: 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: return 1; default: return 0; } } } #endif /* #ifdef USE_LIBPNG */ char screenshot_movie_name[PATH_MAX+1]; char screenshot_movie_file[SCREENSHOT_MOVIE_FILE_MAX+1]; long int screenshot_movie_frame = 0; int screenshot_movie_record = 0; 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; 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; }