From f9b745f3d2b4a066ec73dd5c59a0c614572c2206 Mon Sep 17 00:00:00 2001
From: Seth Hillbrand <seth@kipro-pcb.com>
Date: Thu, 8 Jun 2023 08:54:36 -0700
Subject: [PATCH] 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
---
 common/bitmap_base.cpp                        | 190 +++++++++---------
 common/drawing_sheet/drawing_sheet_parser.cpp |  31 ++-
 common/drawing_sheet/ds_data_model_io.cpp     |  25 ++-
 eeschema/sch_bitmap.cpp                       |   7 -
 eeschema/sch_bitmap.h                         |   2 -
 .../sch_plugins/kicad/sch_sexpr_parser.cpp    |   9 +-
 .../sch_plugins/kicad/sch_sexpr_plugin.cpp    |   8 +-
 .../sch_plugins/legacy/sch_legacy_plugin.cpp  |  11 +-
 include/bitmap_base.h                         |  50 +++--
 include/drawing_sheet/ds_file_versions.h      |   3 +-
 pcbnew/pcb_bitmap.cpp                         |  19 +-
 pcbnew/pcb_bitmap.h                           |  12 +-
 pcbnew/plugins/kicad/pcb_parser.cpp           |  14 +-
 pcbnew/plugins/kicad/pcb_plugin.cpp           |  14 +-
 14 files changed, 213 insertions(+), 182 deletions(-)

diff --git a/common/bitmap_base.cpp b/common/bitmap_base.cpp
index c9d86eeae7..f8df55778e 100644
--- a/common/bitmap_base.cpp
+++ b/common/bitmap_base.cpp
@@ -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() );
+    }
+}
\ No newline at end of file
diff --git a/common/drawing_sheet/drawing_sheet_parser.cpp b/common/drawing_sheet/drawing_sheet_parser.cpp
index ebfcf89df8..cc313a495a 100644
--- a/common/drawing_sheet/drawing_sheet_parser.cpp
+++ b/common/drawing_sheet/drawing_sheet_parser.cpp
@@ -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);
 }
 
diff --git a/common/drawing_sheet/ds_data_model_io.cpp b/common/drawing_sheet/ds_data_model_io.cpp
index 10769670a0..1a2a063fac 100644
--- a/common/drawing_sheet/ds_data_model_io.cpp
+++ b/common/drawing_sheet/ds_data_model_io.cpp
@@ -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.
 }
 
 
diff --git a/eeschema/sch_bitmap.cpp b/eeschema/sch_bitmap.cpp
index 233900475a..b900aa7529 100644
--- a/eeschema/sch_bitmap.cpp
+++ b/eeschema/sch_bitmap.cpp
@@ -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 );
diff --git a/eeschema/sch_bitmap.h b/eeschema/sch_bitmap.h
index 2ccf6655fa..d78ea68236 100644
--- a/eeschema/sch_bitmap.h
+++ b/eeschema/sch_bitmap.h
@@ -58,8 +58,6 @@ public:
         return m_bitmapBase;
     }
 
-    void SetImage( wxImage* aImage );
-
     /**
      * @return the image "zoom" value.
      *  scale = 1.0 = original size of bitmap.
diff --git a/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp b/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp
index 701dc1bbdb..254bad5685 100644
--- a/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp
+++ b/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp
@@ -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;
         }
 
diff --git a/eeschema/sch_plugins/kicad/sch_sexpr_plugin.cpp b/eeschema/sch_plugins/kicad/sch_sexpr_plugin.cpp
index 5c8cf83a52..432c2942a9 100644
--- a/eeschema/sch_plugins/kicad/sch_sexpr_plugin.cpp
+++ b/eeschema/sch_plugins/kicad/sch_sexpr_plugin.cpp
@@ -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.
diff --git a/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp b/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp
index d0c2689d55..4c1becc090 100644
--- a/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp
+++ b/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp
@@ -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" );
                 }
diff --git a/include/bitmap_base.h b/include/bitmap_base.h
index a1d6596c52..562140691b 100644
--- a/include/bitmap_base.h
+++ b/include/bitmap_base.h
@@ -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
diff --git a/include/drawing_sheet/ds_file_versions.h b/include/drawing_sheet/ds_file_versions.h
index 1495393e11..a013399ed8 100644
--- a/include/drawing_sheet/ds_file_versions.h
+++ b/include/drawing_sheet/ds_file_versions.h
@@ -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.
diff --git a/pcbnew/pcb_bitmap.cpp b/pcbnew/pcb_bitmap.cpp
index facc1d0aa3..8f76879696 100644
--- a/pcbnew/pcb_bitmap.cpp
+++ b/pcbnew/pcb_bitmap.cpp
@@ -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 );
diff --git a/pcbnew/pcb_bitmap.h b/pcbnew/pcb_bitmap.h
index 45fad04cf3..68236809ac 100644
--- a/pcbnew/pcb_bitmap.h
+++ b/pcbnew/pcb_bitmap.h
@@ -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;
diff --git a/pcbnew/plugins/kicad/pcb_parser.cpp b/pcbnew/plugins/kicad/pcb_parser.cpp
index 55643ff0d0..cf693e7f21 100644
--- a/pcbnew/plugins/kicad/pcb_parser.cpp
+++ b/pcbnew/plugins/kicad/pcb_parser.cpp
@@ -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;
         }
 
diff --git a/pcbnew/plugins/kicad/pcb_plugin.cpp b/pcbnew/plugins/kicad/pcb_plugin.cpp
index a56d4bc103..f2ff6830d4 100644
--- a/pcbnew/plugins/kicad/pcb_plugin.cpp
+++ b/pcbnew/plugins/kicad/pcb_plugin.cpp
@@ -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.