Allow multiple format image saving

- Keep original image data.  When loading JPEG, this avoid recompression
  that changes file data and decreases image quality
- Allow schematic and page layout editors to store non-PNG data as well
- Move page layout editor to store base64 instead of hex-coded data
This commit is contained in:
Seth Hillbrand 2023-06-08 08:54:36 -07:00
parent 0e382669d0
commit f9b745f3d2
14 changed files with 213 additions and 182 deletions

View File

@ -30,6 +30,10 @@
#include <richio.h>
#include <wx/bitmap.h> // for wxBitmap
#include <wx/mstream.h>
#include <wx/stream.h> // for wxInputStream, wxOutputStream
#include <wx/string.h> // for wxString
#include <wx/wfstream.h> // for wxFileInputStream
BITMAP_BASE::BITMAP_BASE( const VECTOR2I& pos )
@ -65,22 +69,13 @@ BITMAP_BASE::BITMAP_BASE( const BITMAP_BASE& aSchBitmap )
m_image = new wxImage( *aSchBitmap.m_image );
m_bitmap = new wxBitmap( *m_image );
m_originalImage = new wxImage( *aSchBitmap.m_originalImage );
m_imageType = aSchBitmap.m_imageType;
m_imageData = aSchBitmap.m_imageData;
m_imageId = aSchBitmap.m_imageId;
}
}
void BITMAP_BASE::SetImage( wxImage* aImage )
{
delete m_image;
m_image = aImage;
delete m_originalImage;
m_originalImage = new wxImage( *aImage );
rebuildBitmap();
updatePPI();
}
void BITMAP_BASE::rebuildBitmap( bool aResetID )
{
if( m_bitmap )
@ -90,6 +85,7 @@ void BITMAP_BASE::rebuildBitmap( bool aResetID )
if( aResetID )
m_imageId = KIID();
}
@ -120,42 +116,34 @@ void BITMAP_BASE::ImportData( BITMAP_BASE* aItem )
m_isMirroredX = aItem->m_isMirroredX;
m_isMirroredY = aItem->m_isMirroredY;
m_rotation = aItem->m_rotation;
m_imageType = aItem->m_imageType;
m_imageData = aItem->m_imageData;
}
bool BITMAP_BASE::ReadImageFile( wxInputStream& aInStream )
{
// Store the original image data in m_imageData
size_t dataSize = aInStream.GetLength();
m_imageData.SetBufSize( dataSize );
aInStream.Read( m_imageData.GetData(), dataSize );
m_imageData.SetDataLen( dataSize );
std::unique_ptr<wxImage> new_image = std::make_unique<wxImage>();
if( !new_image->LoadFile( aInStream ) )
// Load the image from the stream into new_image
wxMemoryInputStream mem_stream( m_imageData.GetData(), dataSize );
if( !new_image->LoadFile( mem_stream ) )
return false;
delete m_image;
m_image = new_image.release();
delete m_originalImage;
m_originalImage = new wxImage( *m_image );
rebuildBitmap();
updatePPI();
return true;
}
bool BITMAP_BASE::ReadImageFile( const wxString& aFullFilename )
{
wxImage* new_image = new wxImage();
if( !new_image->LoadFile( aFullFilename ) )
{
delete new_image;
return false;
}
m_imageType = new_image->GetType();
delete m_image;
m_image = new_image;
m_image = new_image.release();
// Create a new wxImage object from m_image
delete m_originalImage;
m_originalImage = new wxImage( *m_image );
rebuildBitmap();
updatePPI();
@ -163,78 +151,69 @@ bool BITMAP_BASE::ReadImageFile( const wxString& aFullFilename )
}
bool BITMAP_BASE::SaveData( FILE* aFile ) const
bool BITMAP_BASE::ReadImageFile( wxMemoryBuffer& aBuf )
{
if( m_image )
// Store the original image data in m_imageData
m_imageData = aBuf;
std::unique_ptr<wxImage> new_image = std::make_unique<wxImage>();
// Load the image from the buffer into new_image
wxMemoryInputStream mem_stream( m_imageData.GetData(), m_imageData.GetBufSize() );
if( !new_image->LoadFile( mem_stream ) )
return false;
delete m_image;
m_imageType = new_image->GetType();
m_image = new_image.release();
// Create a new wxImage object from m_image
delete m_originalImage;
m_originalImage = new wxImage( *m_image );
rebuildBitmap();
updatePPI();
return true;
}
bool BITMAP_BASE::ReadImageFile(const wxString& aFullFilename)
{
wxFileInputStream file_stream(aFullFilename);
// Check if the file could be opened successfully
if (!file_stream.IsOk())
return false;
return ReadImageFile(file_stream);
}
bool BITMAP_BASE::SaveImageData( wxOutputStream& aOutStream ) const
{
if( m_imageData.IsEmpty() )
{
wxMemoryOutputStream stream;
// If m_imageData is empty, use wxImage::Save() method to write m_image contents to the stream.
wxBitmapType type = m_imageType == wxBITMAP_TYPE_JPEG ? wxBITMAP_TYPE_JPEG : wxBITMAP_TYPE_PNG;
if( m_imageType == wxBITMAP_TYPE_JPEG )
m_image->SaveFile( stream, wxBITMAP_TYPE_JPEG );
else
// Save as PNG (default
m_image->SaveFile( stream, wxBITMAP_TYPE_PNG );
// Write binary data in hexadecimal form (ASCII)
wxStreamBuffer* buffer = stream.GetOutputStreamBuffer();
char* begin = (char*) buffer->GetBufferStart();
for( int ii = 0; begin < buffer->GetBufferEnd(); begin++, ii++ )
if( !m_image->SaveFile( aOutStream, type ) )
{
if( ii >= 32 )
{
ii = 0;
if( fprintf( aFile, "\n" ) == EOF )
return false;
}
if( fprintf( aFile, "%2.2X ", *begin & 0xFF ) == EOF )
return false;
return false;
}
}
else
{
// Write the contents of m_imageData to the stream.
aOutStream.Write( m_imageData.GetData(), m_imageData.GetBufSize() );
}
return true;
}
void BITMAP_BASE::SaveData( wxArrayString& aPngStrings ) const
{
if( m_image )
{
wxMemoryOutputStream stream;
if( m_imageType == wxBITMAP_TYPE_JPEG )
m_image->SaveFile( stream, wxBITMAP_TYPE_JPEG );
else
// Save as PNG (default
m_image->SaveFile( stream, wxBITMAP_TYPE_PNG );
// Write binary data in hexadecimal form (ASCII)
wxStreamBuffer* buffer = stream.GetOutputStreamBuffer();
char* begin = (char*) buffer->GetBufferStart();
wxString line;
for( int ii = 0; begin < buffer->GetBufferEnd(); begin++, ii++ )
{
if( ii >= 32 )
{
ii = 0;
aPngStrings.Add( line );
line.Empty();
}
line << wxString::Format( wxT( "%2.2X " ), *begin & 0xFF );
}
// Add last line:
if( !line.IsEmpty() )
aPngStrings.Add( line );
}
}
bool BITMAP_BASE::LoadData( LINE_READER& aLine, wxString& aErrorMsg )
bool BITMAP_BASE::LoadLegacyData( LINE_READER& aLine, wxString& aErrorMsg )
{
wxMemoryOutputStream stream;
char* line;
@ -258,6 +237,7 @@ bool BITMAP_BASE::LoadData( LINE_READER& aLine, wxString& aErrorMsg )
m_image->LoadFile( istream, wxBITMAP_TYPE_ANY );
m_bitmap = new wxBitmap( *m_image );
m_originalImage = new wxImage( *m_image );
UpdateImageDataBuffer();
break;
}
@ -450,6 +430,7 @@ void BITMAP_BASE::Mirror( bool aVertically )
m_isMirroredX = !m_isMirroredX;
rebuildBitmap( false );
UpdateImageDataBuffer();
}
}
@ -474,6 +455,7 @@ void BITMAP_BASE::Rotate( bool aRotateCCW )
m_rotation += ( aRotateCCW ? -ANGLE_90 : ANGLE_90 );
rebuildBitmap( false );
UpdateImageDataBuffer();
}
}
@ -485,6 +467,7 @@ void BITMAP_BASE::ConvertToGreyscale()
*m_image = m_image->ConvertToGreyscale();
*m_originalImage = m_originalImage->ConvertToGreyscale();
rebuildBitmap();
UpdateImageDataBuffer();
}
}
@ -502,3 +485,20 @@ void BITMAP_BASE::PlotImage( PLOTTER* aPlotter, const VECTOR2I& aPos,
aPlotter->SetCurrentLineWidth( aDefaultPensize );
aPlotter->PlotImage( *m_image, aPos, GetScalingFactor() );
}
void BITMAP_BASE::UpdateImageDataBuffer()
{
if( m_image )
{
wxMemoryOutputStream stream;
wxBitmapType type = m_imageType == wxBITMAP_TYPE_JPEG ? wxBITMAP_TYPE_JPEG : wxBITMAP_TYPE_PNG;
if( !m_image->SaveFile( stream, type ) )
return;
m_imageData.GetWriteBuf( stream.GetLength() );
stream.CopyTo( m_imageData.GetData(), stream.GetLength() );
m_imageData.SetDataLen( stream.GetLength() );
}
}

View File

@ -24,8 +24,10 @@
*/
#include <charconv>
#include <wx/base64.h>
#include <wx/ffile.h>
#include <wx/log.h>
#include <eda_item.h>
#include <locale_io.h>
#include <string_utils.h>
@ -512,6 +514,33 @@ void DRAWING_SHEET_PARSER::parseBitmap( DS_DATA_ITEM_BITMAP * aItem )
NeedRIGHT();
break;
case T_data:
{
token = NextTok();
wxString data;
// Reserve 512K because most image files are going to be larger than the default
// 1K that wxString reserves.
data.reserve( 1 << 19 );
while( token != T_RIGHT )
{
if( !IsSymbol( token ) )
Expecting( "base64 image data" );
data += FromUTF8();
token = NextTok();
}
wxMemoryBuffer buffer = wxBase64Decode( data );
if( !aItem->m_ImageBitmap->ReadImageFile( buffer ) )
THROW_IO_ERROR( _( "Failed to read image data." ) );
break;
}
case T_pngdata:
readPngdata( aItem );
break;
@ -556,7 +585,7 @@ void DRAWING_SHEET_PARSER::readPngdata( DS_DATA_ITEM_BITMAP * aItem )
wxString msg;
STRING_LINE_READER str_reader( tmp, wxT("Png kicad_wks data") );
if( ! aItem->m_ImageBitmap->LoadData( str_reader, msg ) )
if( ! aItem->m_ImageBitmap->LoadLegacyData( str_reader, msg ) )
wxLogMessage(msg);
}

View File

@ -35,6 +35,7 @@
#include <drawing_sheet/ds_file_versions.h>
#include <font/font.h>
#include <wx/base64.h>
#include <wx/msgdlg.h>
using namespace DRAWINGSHEET_T;
@ -417,16 +418,26 @@ void DS_DATA_MODEL_IO::format( DS_DATA_ITEM_BITMAP* aItem, int aNestLevel ) cons
m_out->Print( 0, " (comment %s)\n", m_out->Quotew( aItem->m_Info ).c_str() );
// Write image in png readable format
m_out->Print( aNestLevel, "(pngdata\n" );
wxArrayString pngStrings;
aItem->m_ImageBitmap->SaveData( pngStrings );
m_out->Print( aNestLevel, "(data" );
for( unsigned ii = 0; ii < pngStrings.GetCount(); ii++ )
m_out->Print( aNestLevel+1, "(data \"%s\")\n", TO_UTF8(pngStrings[ii]) );
wxString out = wxBase64Encode( aItem->m_ImageBitmap->GetImageDataBuffer() );
m_out->Print( aNestLevel+1, ")\n" );
// Apparently the MIME standard character width for base64 encoding is 76 (unconfirmed)
// so use it in a vain attempt to be standard like.
#define MIME_BASE64_LENGTH 76
m_out->Print( aNestLevel, ")\n" );
size_t first = 0;
while( first < out.Length() )
{
m_out->Print( 0, "\n" );
m_out->Print( aNestLevel + 1, "%s", TO_UTF8( out( first, MIME_BASE64_LENGTH ) ) );
first += MIME_BASE64_LENGTH;
}
m_out->Print( 0, "\n" );
m_out->Print( aNestLevel, ")\n" ); // Closes data token.
m_out->Print( aNestLevel, ")\n" ); // Closes bitmap token.
}

View File

@ -92,13 +92,6 @@ bool SCH_BITMAP::ReadImageFile( const wxString& aFullFilename )
}
void SCH_BITMAP::SetImage( wxImage* aImage )
{
m_bitmapBase->SetImage( aImage );
m_bitmapBase->SetPixelSizeIu( 254000.0 / m_bitmapBase->GetPPI() );
}
EDA_ITEM* SCH_BITMAP::Clone() const
{
return new SCH_BITMAP( *this );

View File

@ -58,8 +58,6 @@ public:
return m_bitmapBase;
}
void SetImage( wxImage* aImage );
/**
* @return the image "zoom" value.
* scale = 1.0 = original size of bitmap.

View File

@ -3056,11 +3056,10 @@ SCH_BITMAP* SCH_SEXPR_PARSER::parseImage()
}
wxMemoryBuffer buffer = wxBase64Decode( data );
wxMemoryOutputStream stream( buffer.GetData(), buffer.GetBufSize() );
wxImage* image = new wxImage();
wxMemoryInputStream istream( stream );
image->LoadFile( istream, wxBITMAP_TYPE_PNG );
bitmap->SetImage( image );
if( !bitmap->GetImage()->ReadImageFile( buffer ) )
THROW_IO_ERROR( _( "Failed to read image data." ) );
break;
}

View File

@ -952,13 +952,7 @@ void SCH_SEXPR_PLUGIN::saveBitmap( SCH_BITMAP* aBitmap, int aNestLevel )
m_out->Print( aNestLevel + 1, "(data" );
wxMemoryOutputStream stream;
image->SaveFile( stream, wxBITMAP_TYPE_PNG );
// Write binary data in hexadecimal form (ASCII)
wxStreamBuffer* buffer = stream.GetOutputStreamBuffer();
wxString out = wxBase64Encode( buffer->GetBufferStart(), buffer->GetBufferSize() );
wxString out = wxBase64Encode( aBitmap->GetImage()->GetImageDataBuffer() );
// Apparently the MIME standard character width for base64 encoding is 76 (unconfirmed)
// so use it in a vein attempt to be standard like.

View File

@ -676,7 +676,7 @@ SCH_BITMAP* SCH_LEGACY_PLUGIN::loadBitmap( LINE_READER& aReader )
}
else if( strCompare( "Data", line, &line ) )
{
wxMemoryOutputStream stream;
wxMemoryBuffer buffer;
while( line )
{
@ -688,12 +688,7 @@ SCH_BITMAP* SCH_LEGACY_PLUGIN::loadBitmap( LINE_READER& aReader )
if( strCompare( "EndData", line ) )
{
// all the PNG date is read.
// We expect here m_image and m_bitmap are void
wxImage* image = new wxImage();
wxMemoryInputStream istream( stream );
image->LoadFile( istream, wxBITMAP_TYPE_PNG );
bitmap->SetImage( image );
bitmap->GetImage()->ReadImageFile( buffer );
// Legacy file formats assumed 300 image PPI at load.
BITMAP_BASE* bitmapImage = bitmap->GetImage();
@ -716,7 +711,7 @@ SCH_BITMAP* SCH_LEGACY_PLUGIN::loadBitmap( LINE_READER& aReader )
int value = 0;
if( sscanf( line, "%X", &value ) == 1 )
stream.PutC( (char) value );
buffer.AppendByte( (char) value );
else
THROW_IO_ERROR( "invalid PNG data" );
}

View File

@ -69,8 +69,6 @@ public:
const wxImage* GetOriginalImageData() const { return m_originalImage; }
void SetImage( wxImage* aImage );
double GetScale() const { return m_scale; }
void SetScale( double aScale ) { m_scale = aScale; }
@ -159,25 +157,27 @@ public:
bool ReadImageFile( wxInputStream& aInStream );
/**
* Write the bitmap data to \a aFile.
* Reads and stores in memory an image file.
*
* The file format is png, in hexadecimal form. If the hexadecimal data is converted to
* binary it gives exactly a .png image data.
* Initialize the bitmap format used to draw this item.
*
* @param aFile The FILE to write to.
* @return true if success writing else false.
* Supported images formats are format supported by wxImage if all handlers are loaded.
* By default, .png, .jpeg are always loaded.
*
* @param aBuf a memory buffer containing the file data.
* @return true if success reading else false.
*/
bool SaveData( FILE* aFile ) const;
bool ReadImageFile( wxMemoryBuffer& aBuf );
/**
* Write the bitmap data to an array string.
*
* The format is png, in Hexadecimal form. If the hexadecimal data is converted to binary
* it gives exactly a .png image data.
*
* @param aPngStrings The wxArrayString to write to.
*/
void SaveData( wxArrayString& aPngStrings ) const;
* Write the bitmap data to \a aOutStream.
*
* This writes binary data, not hexadecimal strings
*
* @param aOutStream The output stream to write to.
* @return true if success writing else false.
*/
bool SaveImageData( wxOutputStream& aOutStream ) const;
/**
* Load an image data saved by #SaveData.
@ -189,7 +189,7 @@ public:
* png bitmap data.
* @return true if the bitmap loaded successfully.
*/
bool LoadData( LINE_READER& aLine, wxString& aErrorMsg );
bool LoadLegacyData( LINE_READER& aLine, wxString& aErrorMsg );
/**
* Mirror image vertically (i.e. relative to its horizontal X axis ) or horizontally (i.e
@ -234,6 +234,16 @@ public:
*/
void SetImageType( wxBitmapType aType ) { m_imageType = aType; }
/**
* @return the image data buffer.
*/
const wxMemoryBuffer& GetImageDataBuffer() const { return m_imageData; }
/**
* Resets the image data buffer using the current image data.
*/
void UpdateImageDataBuffer();
private:
/*
* Rebuild the internal bitmap used to draw/plot image.
@ -248,8 +258,10 @@ private:
double m_scale; // The scaling factor of the bitmap
// With m_pixelSizeIu, controls the actual draw size
wxImage* m_image; // the raw image data
wxBitmapType m_imageType; // the image type (png, jpeg, etc.)
wxMemoryBuffer m_imageData; // The original image data, in its original format
wxBitmapType m_imageType; // the image type (png, jpeg, etc.)
wxImage* m_image; // the raw, uncompressed image data
wxImage* m_originalImage; // Raw image data, not transformed by rotate/mirror
wxBitmap* m_bitmap; // the bitmap used to draw/plot image
double m_pixelSizeIu; // The scaling factor of the bitmap

View File

@ -31,4 +31,5 @@
*/
//#define SEXPR_WORKSHEET_FILE_VERSION 20210606 // Initial version.
#define SEXPR_WORKSHEET_FILE_VERSION 20220228 // Font support.
//#define SEXPR_WORKSHEET_FILE_VERSION 20220228 // Font support.
#define SEXPR_WORKSHEET_FILE_VERSION 20230607 // Save images as base64.

View File

@ -82,13 +82,6 @@ PCB_BITMAP& PCB_BITMAP::operator=( const BOARD_ITEM& aItem )
}
void PCB_BITMAP::SetImage( wxImage* aImage )
{
m_bitmapBase->SetImage( aImage );
m_bitmapBase->SetPixelSizeIu( (float) pcbIUScale.MilsToIU( 1000 ) / m_bitmapBase->GetPPI() );
}
bool PCB_BITMAP::ReadImageFile( const wxString& aFullFilename )
{
if( m_bitmapBase->ReadImageFile( aFullFilename ) )
@ -101,6 +94,18 @@ bool PCB_BITMAP::ReadImageFile( const wxString& aFullFilename )
}
bool PCB_BITMAP::ReadImageFile( wxMemoryBuffer& aBuffer )
{
if( m_bitmapBase->ReadImageFile( aBuffer ) )
{
m_bitmapBase->SetPixelSizeIu( (float) pcbIUScale.MilsToIU( 1000 ) / m_bitmapBase->GetPPI() );
return true;
}
return false;
}
EDA_ITEM* PCB_BITMAP::Clone() const
{
return new PCB_BITMAP( *this );

View File

@ -65,8 +65,6 @@ public:
return m_bitmapBase;
}
void SetImage( wxImage* aImage );
/**
* @return the image "zoom" value.
* scale = 1.0 = original size of bitmap.
@ -111,6 +109,16 @@ public:
*/
bool ReadImageFile( const wxString& aFullFilename );
/**
* Read and store an image file.
*
* Initialize the bitmap used to draw this item format.
*
* @param aBuf is the memory buffer containing the image file to read.
* @return true if success reading else false.
*/
bool ReadImageFile( wxMemoryBuffer& aBuf );
void Move( const VECTOR2I& aMoveVector ) override { m_pos += aMoveVector; }
void Flip( const VECTOR2I& aCentre, bool aFlipLeftRight ) override;

View File

@ -2967,9 +2967,9 @@ PCB_BITMAP* PCB_PARSER::parsePCB_BITMAP( BOARD_ITEM* aParent )
wxString data;
// Reserve 128K because most image files are going to be larger than the default
// Reserve 512K because most image files are going to be larger than the default
// 1K that wxString reserves.
data.reserve( 1 << 17 );
data.reserve( 1 << 19 );
while( token != T_RIGHT )
{
@ -2981,12 +2981,10 @@ PCB_BITMAP* PCB_PARSER::parsePCB_BITMAP( BOARD_ITEM* aParent )
}
wxMemoryBuffer buffer = wxBase64Decode( data );
wxMemoryOutputStream stream( buffer.GetData(), buffer.GetBufSize() );
wxImage* image = new wxImage();
wxMemoryInputStream istream( stream );
image->LoadFile( istream );
bitmap->SetImage( image );
bitmap->MutableImage()->SetImageType( image->GetType() );
if( !bitmap->ReadImageFile( buffer ) )
THROW_IO_ERROR( _( "Failed to read image data." ) );
break;
}

View File

@ -1040,19 +1040,7 @@ void PCB_PLUGIN::format( const PCB_BITMAP* aBitmap, int aNestLevel ) const
m_out->Print( aNestLevel + 1, "(data" );
wxMemoryOutputStream stream;
wxBitmapType type = wxBITMAP_TYPE_PNG;
// Save the image in the same format as the original file
// if it was a JPEG. Otherwise, save it as a PNG.
if( aBitmap->GetImage()->GetImageType() == wxBITMAP_TYPE_JPEG )
type = wxBITMAP_TYPE_JPEG;
image->SaveFile( stream, type );
// Write binary data in hexadecimal form (ASCII)
wxStreamBuffer* buffer = stream.GetOutputStreamBuffer();
wxString out = wxBase64Encode( buffer->GetBufferStart(), buffer->GetBufferSize() );
wxString out = wxBase64Encode( aBitmap->GetImage()->GetImageDataBuffer() );
// Apparently the MIME standard character width for base64 encoding is 76 (unconfirmed)
// so use it in a vain attempt to be standard like.