7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-02 18:46:54 +00:00

Eeschema/pcbnew: fix pasted image saving

The m_imageData buffer in the BITMAP_BASE is an
internal implementation - the public API to persist the
data to a stream appears to be SaveImageData - so use that,
which hides the implementation (and generates the image data
"live" when needed).

Remove the public access to the m_imageData buffer as it isn't
needed for public use, and also is misleading.

Also break out the formatting of the data into KICAD_FORMAT,
as it's currently replicated in eeschema/pcb/pagelayout
formatting code.

Fixes: https://gitlab.com/kicad/code/kicad/-/issues/19772
This commit is contained in:
John Beard 2025-01-31 21:59:29 +08:00
parent c5120b9090
commit 636c6c4efb
13 changed files with 148 additions and 79 deletions

View File

@ -239,7 +239,7 @@ bool BITMAP_BASE::LoadLegacyData( LINE_READER& aLine, wxString& aErrorMsg )
m_image->LoadFile( istream, wxBITMAP_TYPE_ANY ); m_image->LoadFile( istream, wxBITMAP_TYPE_ANY );
m_bitmap = new wxBitmap( *m_image ); m_bitmap = new wxBitmap( *m_image );
m_originalImage = new wxImage( *m_image ); m_originalImage = new wxImage( *m_image );
UpdateImageDataBuffer(); updateImageDataBuffer();
break; break;
} }
@ -433,7 +433,7 @@ void BITMAP_BASE::Mirror( FLIP_DIRECTION aFlipDirection )
m_isMirroredX = !m_isMirroredX; m_isMirroredX = !m_isMirroredX;
rebuildBitmap( false ); rebuildBitmap( false );
UpdateImageDataBuffer(); updateImageDataBuffer();
} }
} }
@ -458,7 +458,7 @@ void BITMAP_BASE::Rotate( bool aRotateCCW )
m_rotation += ( aRotateCCW ? ANGLE_90 : -ANGLE_90 ); m_rotation += ( aRotateCCW ? ANGLE_90 : -ANGLE_90 );
rebuildBitmap( false ); rebuildBitmap( false );
UpdateImageDataBuffer(); updateImageDataBuffer();
} }
} }
@ -470,7 +470,7 @@ void BITMAP_BASE::ConvertToGreyscale()
*m_image = m_image->ConvertToGreyscale(); *m_image = m_image->ConvertToGreyscale();
*m_originalImage = m_originalImage->ConvertToGreyscale(); *m_originalImage = m_originalImage->ConvertToGreyscale();
rebuildBitmap(); rebuildBitmap();
UpdateImageDataBuffer(); updateImageDataBuffer();
} }
} }
@ -490,7 +490,7 @@ void BITMAP_BASE::PlotImage( PLOTTER* aPlotter, const VECTOR2I& aPos,
} }
void BITMAP_BASE::UpdateImageDataBuffer() void BITMAP_BASE::updateImageDataBuffer()
{ {
if( m_image ) if( m_image )
{ {

View File

@ -34,10 +34,11 @@
#include <drawing_sheet/drawing_sheet_lexer.h> #include <drawing_sheet/drawing_sheet_lexer.h>
#include <drawing_sheet/ds_file_versions.h> #include <drawing_sheet/ds_file_versions.h>
#include <font/font.h> #include <font/font.h>
#include <io/kicad/kicad_io_utils.h>
#include <string_utils.h> #include <string_utils.h>
#include <wx/base64.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <wx/mstream.h>
using namespace DRAWINGSHEET_T; using namespace DRAWINGSHEET_T;
@ -422,21 +423,11 @@ void DS_DATA_MODEL_IO::format( DS_DATA_ITEM_BITMAP* aItem ) const
// Write image in png readable format // Write image in png readable format
m_out->Print( "(data" ); m_out->Print( "(data" );
wxString out = wxBase64Encode( aItem->m_ImageBitmap->GetImageDataBuffer() ); wxMemoryOutputStream stream;
aItem->m_ImageBitmap->SaveImageData( stream );
// Apparently the MIME standard character width for base64 encoding is 76 (unconfirmed) KICAD_FORMAT::FormatStreamData( *m_out, *stream.GetOutputStreamBuffer() );
// so use it in a vain attempt to be standard like.
#define MIME_BASE64_LENGTH 76
size_t first = 0;
while( first < out.Length() )
{
m_out->Print( "\n\"%s\"", TO_UTF8( out( first, MIME_BASE64_LENGTH ) ) );
first += MIME_BASE64_LENGTH;
}
m_out->Print( ")" ); // Closes data token.
m_out->Print( ")" ); // Closes bitmap token. m_out->Print( ")" ); // Closes bitmap token.
} }

View File

@ -17,10 +17,16 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "io/kicad/kicad_io_utils.h"
// For some reason wxWidgets is built with wxUSE_BASE64 unset so expose the wxWidgets
// base64 code.
#define wxUSE_BASE64 1
#include <wx/base64.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <kiid.h> #include <kiid.h>
#include <io/kicad/kicad_io_utils.h>
#include <richio.h> #include <richio.h>
#include <string_utils.h> #include <string_utils.h>
@ -37,6 +43,29 @@ void FormatUuid( OUTPUTFORMATTER* aOut, const KIID& aUuid )
aOut->Print( "(uuid %s)", aOut->Quotew( aUuid.AsString() ).c_str() ); aOut->Print( "(uuid %s)", aOut->Quotew( aUuid.AsString() ).c_str() );
} }
void FormatStreamData( OUTPUTFORMATTER& aOut, const wxStreamBuffer& aStream )
{
aOut.Print( "(data" );
const wxString out = wxBase64Encode( aStream.GetBufferStart(), aStream.GetBufferSize() );
// Apparently the MIME standard character width for base64 encoding is 76 (unconfirmed)
// so use it in a vein attempt to be standard like.
static constexpr unsigned MIME_BASE64_LENGTH = 76;
size_t first = 0;
while( first < out.Length() )
{
aOut.Print( "\n\"%s\"", TO_UTF8( out( first, MIME_BASE64_LENGTH ) ) );
first += MIME_BASE64_LENGTH;
}
aOut.Print( ")" ); // Closes data token.
}
/* /*
* Formatting rules: * Formatting rules:
* - All extra (non-indentation) whitespace is trimmed * - All extra (non-indentation) whitespace is trimmed

View File

@ -22,10 +22,6 @@
#include <algorithm> #include <algorithm>
// For some reason wxWidgets is built with wxUSE_BASE64 unset so expose the wxWidgets
// base64 code.
#define wxUSE_BASE64 1
#include <wx/base64.h>
#include <wx/log.h> #include <wx/log.h>
#include <wx/mstream.h> #include <wx/mstream.h>
@ -958,23 +954,11 @@ void SCH_IO_KICAD_SEXPR::saveBitmap( const SCH_BITMAP& aBitmap )
KICAD_FORMAT::FormatUuid( m_out, aBitmap.m_Uuid ); KICAD_FORMAT::FormatUuid( m_out, aBitmap.m_Uuid );
m_out->Print( "(data" ); wxMemoryOutputStream stream;
bitmapBase.SaveImageData( stream );
wxString out = wxBase64Encode( bitmapBase.GetImageDataBuffer() ); KICAD_FORMAT::FormatStreamData( *m_out, *stream.GetOutputStreamBuffer() );
// Apparently the MIME standard character width for base64 encoding is 76 (unconfirmed)
// so use it in a vein attempt to be standard like.
#define MIME_BASE64_LENGTH 76
size_t first = 0;
while( first < out.Length() )
{
m_out->Print( "\n\"%s\"", TO_UTF8( out( first, MIME_BASE64_LENGTH ) ) );
first += MIME_BASE64_LENGTH;
}
m_out->Print( ")" ); // Closes data token.
m_out->Print( ")" ); // Closes image token. m_out->Print( ")" ); // Closes image token.
} }

View File

@ -1657,7 +1657,14 @@ int SCH_EDITOR_CONTROL::Paste( const TOOL_EVENT& aEvent )
{ {
// Just image data // Just image data
auto bitmap = std::make_unique<SCH_BITMAP>(); auto bitmap = std::make_unique<SCH_BITMAP>();
bitmap->GetReferenceImage().SetImage( *clipImg );
bool ok = bitmap->GetReferenceImage().SetImage( *clipImg );
if( !ok )
{
delete tempScreen;
return 0;
}
tempScreen->Append( bitmap.release() ); tempScreen->Append( bitmap.release() );
} }

View File

@ -240,17 +240,12 @@ public:
*/ */
void SetImageType( wxBitmapType aType ) { m_imageType = aType; } void SetImageType( wxBitmapType aType ) { m_imageType = aType; }
/** private:
* @return the image data buffer.
*/
const wxMemoryBuffer& GetImageDataBuffer() const { return m_imageData; }
/** /**
* Resets the image data buffer using the current image data. * Resets the image data buffer using the current image data.
*/ */
void UpdateImageDataBuffer(); void updateImageDataBuffer();
private:
/** /**
* Rebuild the internal bitmap used to draw/plot image. * Rebuild the internal bitmap used to draw/plot image.
* *

View File

@ -17,11 +17,13 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef KICAD_IO_UTILS_H #pragma once
#define KICAD_IO_UTILS_H
#include <wx/stream.h>
#include <wx/string.h> #include <wx/string.h>
#include <kicommon.h>
class OUTPUTFORMATTER; class OUTPUTFORMATTER;
class KIID; class KIID;
@ -38,8 +40,11 @@ KICOMMON_API void FormatBool( OUTPUTFORMATTER* aOut, const wxString& aKey, bool
KICOMMON_API void FormatUuid( OUTPUTFORMATTER* aOut, const KIID& aUuid ); KICOMMON_API void FormatUuid( OUTPUTFORMATTER* aOut, const KIID& aUuid );
/**
* Write binary data to the formatter as base 64 encoded string.
*/
KICOMMON_API void FormatStreamData( OUTPUTFORMATTER& aOut, const wxStreamBuffer& aStream );
KICOMMON_API void Prettify( std::string& aSource, bool aCompactSave ); KICOMMON_API void Prettify( std::string& aSource, bool aCompactSave );
} // namespace KICAD_FORMAT } // namespace KICAD_FORMAT
#endif //KICAD_IO_UTILS_H

View File

@ -22,9 +22,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
// base64 code. Needed for PCB_REFERENCE_IMAGE
#define wxUSE_BASE64 1
#include <wx/base64.h>
#include <wx/dir.h> #include <wx/dir.h>
#include <wx/ffile.h> #include <wx/ffile.h>
#include <wx/log.h> #include <wx/log.h>
@ -1066,23 +1063,10 @@ void PCB_IO_KICAD_SEXPR::format( const PCB_REFERENCE_IMAGE* aBitmap ) const
if( aBitmap->IsLocked() ) if( aBitmap->IsLocked() )
KICAD_FORMAT::FormatBool( m_out, "locked", true ); KICAD_FORMAT::FormatBool( m_out, "locked", true );
m_out->Print( "(data" ); wxMemoryOutputStream ostream;
refImage.GetImage().SaveImageData( ostream );
wxString out = wxBase64Encode( refImage.GetImage().GetImageDataBuffer() ); KICAD_FORMAT::FormatStreamData( *m_out, *ostream.GetOutputStreamBuffer() );
// 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
size_t first = 0;
while( first < out.Length() )
{
m_out->Print( "\n\"%s\"", TO_UTF8( out( first, MIME_BASE64_LENGTH ) ) );
first += MIME_BASE64_LENGTH;
}
m_out->Print( ")" ); // Closes data token.
KICAD_FORMAT::FormatUuid( m_out, aBitmap->m_Uuid ); KICAD_FORMAT::FormatUuid( m_out, aBitmap->m_Uuid );
m_out->Print( ")" ); // Closes image token. m_out->Print( ")" ); // Closes image token.

View File

@ -142,6 +142,7 @@ std::ostream& boost_test_print_type( std::ostream& os, std::pair<K, V> const& aP
*/ */
std::ostream& boost_test_print_type( std::ostream& os, wxPoint const& aVec ); std::ostream& boost_test_print_type( std::ostream& os, wxPoint const& aVec );
std::ostream& boost_test_print_type( std::ostream& os, wxSize const& aSize );
namespace KI_TEST namespace KI_TEST
{ {

View File

@ -31,6 +31,13 @@ std::ostream& boost_test_print_type( std::ostream& os, wxPoint const& aPt )
} }
std::ostream& boost_test_print_type( std::ostream& os, wxSize const& aSize )
{
os << "wxSize[ x=\"" << aSize.x << "\" y=\"" << aSize.y << "\" ]";
return os;
}
std::string KI_TEST::GetEeschemaTestDataDir() std::string KI_TEST::GetEeschemaTestDataDir()
{ {
const char* env = std::getenv( "KICAD_TEST_EESCHEMA_DATA_DIR" ); const char* env = std::getenv( "KICAD_TEST_EESCHEMA_DATA_DIR" );

View File

@ -244,4 +244,37 @@ BOOST_AUTO_TEST_CASE( MirrorImage )
} }
} }
/**
* Check setting image data by SetImage produces saveable data
* via SaveImageData.
*
* Regression test for: https://gitlab.com/kicad/code/kicad/-/issues/19772
*/
BOOST_AUTO_TEST_CASE( SetImageOutputsData )
{
BITMAP_BASE bitmap;
wxImage img( 2, 2 );
img.SetRGB( 0, 0, 0, 255, 0 );
img.SetRGB( 1, 0, 0, 0, 255 );
img.SetRGB( 0, 1, 255, 0, 0 );
img.SetRGB( 1, 1, 0, 0, 255 );
// Set the wxImage directly, not via a stream/file
// (this happens, e.g. when the clipboard gives you a wxImage)
bitmap.SetImage( img );
wxMemoryOutputStream mos;
BOOST_CHECK( bitmap.SaveImageData( mos ) );
BOOST_REQUIRE( mos.GetSize() > 0 );
// check the output is the same as the input
wxMemoryInputStream mis( mos );
wxImage img2;
BOOST_CHECK( img2.LoadFile( mis, wxBITMAP_TYPE_PNG ) );
BOOST_CHECK_PREDICATE( KI_TEST::ImagesHaveSamePixels, (img) ( img2 ) );
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View File

@ -28,15 +28,7 @@
namespace KI_TEST namespace KI_TEST
{ {
/**
* Predicate to check an image pixel matches color and alpha
*
* @param aImage the image to check
* @param aX pixel x-coordinate
* @param aY pixel y-coordinate
* @param aColor expected color (alpha is 1.0 if image doesn't support alpha)
* @return true if colour match
*/
bool IsImagePixelOfColor( const wxImage& aImage, int aX, int aY, const KIGFX::COLOR4D& aColor ) bool IsImagePixelOfColor( const wxImage& aImage, int aX, int aY, const KIGFX::COLOR4D& aColor )
{ {
const wxSize imageSize = aImage.GetSize(); const wxSize imageSize = aImage.GetSize();
@ -64,6 +56,40 @@ bool IsImagePixelOfColor( const wxImage& aImage, int aX, int aY, const KIGFX::CO
return true; return true;
} }
bool ImagesHaveSamePixels( const wxImage& aImgA, const wxImage& aImgB )
{
if( aImgA.GetSize() != aImgB.GetSize() )
{
BOOST_TEST_INFO( "Image sizes differ: " << aImgA.GetSize() << " vs " << aImgB.GetSize() );
return false;
}
for( int y = 0; y < aImgA.GetHeight(); ++y )
{
for( int x = 0; x < aImgA.GetWidth(); ++x )
{
const int rA = aImgA.GetRed( x, y );
const int gA = aImgA.GetGreen( x, y );
const int bA = aImgA.GetBlue( x, y );
const int rB = aImgB.GetRed( x, y );
const int gB = aImgB.GetGreen( x, y );
const int bB = aImgB.GetBlue( x, y );
if( rA != rB || gA != gB || bA != bB )
{
BOOST_TEST_INFO( "Pixel (" << x << ", " << y << ") differs: "
<< "A(" << rA << ", " << gA << ", " << bA << ") "
<< "B(" << rB << ", " << gB << ", " << bB << ")" );
return false;
}
}
}
return true;
}
} // namespace KI_TEST } // namespace KI_TEST

View File

@ -43,6 +43,13 @@ namespace KI_TEST
*/ */
bool IsImagePixelOfColor( const wxImage& aImage, int aX, int aY, const KIGFX::COLOR4D& aColor ); bool IsImagePixelOfColor( const wxImage& aImage, int aX, int aY, const KIGFX::COLOR4D& aColor );
/**
* Check if an image is identical to another image, pixel by pixel
*
* (this is exhaustive, avoid using it for large images)
*/
bool ImagesHaveSamePixels( const wxImage& aImgA, const wxImage& aImgB );
} // namespace KI_TEST } // namespace KI_TEST