diff --git a/include/wx/dataobj.h b/include/wx/dataobj.h index 4091f7b766..ef0c385df5 100644 --- a/include/wx/dataobj.h +++ b/include/wx/dataobj.h @@ -36,7 +36,9 @@ wxTextDataObject | wxBitmapDataObject | wxCustomDataObject - + | + | + wxImageDataObject */ // ============================================================================ @@ -545,6 +547,22 @@ private: wxDECLARE_NO_COPY_CLASS(wxCustomDataObject); }; +// ---------------------------------------------------------------------------- +// wxImageDataObject - data object for wxImage +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_CORE wxImageDataObject : public wxCustomDataObject +{ +public: + explicit wxImageDataObject(const wxImage& image = wxNullImage); + + void SetImage(const wxImage& image); + wxImage GetImage() const; + +private: + wxDECLARE_NO_COPY_CLASS(wxImageDataObject); +}; + // ---------------------------------------------------------------------------- // include platform-specific declarations of wxXXXBase classes // ---------------------------------------------------------------------------- diff --git a/include/wx/defs.h b/include/wx/defs.h index 101a016727..1881ac247f 100644 --- a/include/wx/defs.h +++ b/include/wx/defs.h @@ -2153,6 +2153,7 @@ enum wxDataFormatId wxDF_LOCALE = 16, wxDF_PRIVATE = 20, wxDF_HTML = 30, /* Note: does not correspond to CF_ constant */ + wxDF_PNG = 31, /* Note: does not correspond to CF_ constant */ wxDF_MAX }; diff --git a/interface/wx/dataobj.h b/interface/wx/dataobj.h index d01b84d5d4..90a6b7766f 100644 --- a/interface/wx/dataobj.h +++ b/interface/wx/dataobj.h @@ -34,6 +34,9 @@ A list of filenames.} @itemdef{wxDF_HTML, An HTML string. This is currently only valid on Mac and MSW.} + @itemdef{wxDF_PNG, + A PNG file. This is valid only on MSW. This constant is available + since wxWidgets 3.1.5.} @endDefList As mentioned above, these standard formats may be passed to any function @@ -620,6 +623,42 @@ public: +/** + @class wxImageDataObject + + wxImageDataObject is a specialization of wxDataObject for image data. + It can be used e.g. when you need to put on and retrieve from the clipboard + a wxImage with its metadata (like image resolution). + + @since 3.1.5 + + @library{wxcore} + @category{dnd} + + @see @ref overview_dnd, wxDataObject, wxCustomDataObject, wxBitmapDataObject +*/ +class wxImageDataObject : public wxCustomDataObject +{ +public: + /** + Constructor, optionally passing an image (otherwise use SetImage() + later). + */ + explicit wxImageDataObject(const wxImage& image = wxNullImage); + + /** + Returns the image associated with the data object. + */ + wxImage GetImage() const; + + /** + Sets the image stored by the data object. + */ + void SetImage(const wxImage& image); +}; + + + /** @class wxURLDataObject diff --git a/samples/image/image.cpp b/samples/image/image.cpp index 51bbdd4165..4ecbf16203 100644 --- a/samples/image/image.cpp +++ b/samples/image/image.cpp @@ -91,6 +91,8 @@ public: #if wxUSE_CLIPBOARD void OnCopy(wxCommandEvent& event); void OnPaste(wxCommandEvent& event); + void OnCopyImage(wxCommandEvent& evt); + void OnPasteImage(wxCommandEvent& evt); #endif // wxUSE_CLIPBOARD MyCanvas *m_canvas; @@ -124,8 +126,39 @@ class MyImageFrame : public wxFrame public: MyImageFrame(wxFrame *parent, const wxString& desc, const wxImage& image, double scale = 1.0) { - Create(parent, desc, wxBitmap(image, wxBITMAP_SCREEN_DEPTH, scale), - image.GetImageCount(desc)); + // Retrieve image info + wxString info; + int xres, yres; + switch ( GetResolutionFromOptions(image, &xres, &yres) ) + { + case wxIMAGE_RESOLUTION_NONE: + break; + + case wxIMAGE_RESOLUTION_CM: + // convert to DPI + xres = wxRound(xres / 10.0 * inches2mm); + yres = wxRound(yres / 10.0 * inches2mm); + wxFALLTHROUGH; + + case wxIMAGE_RESOLUTION_INCHES: + info = wxString::Format("DPI %i x %i", xres, yres); + break; + + default: + wxFAIL_MSG("unexpected image resolution units"); + break; + } + + int numImages = desc.StartsWith("Clipboard") ? 1 : image.GetImageCount(desc); + if ( numImages > 1 ) + { + if ( !info.empty() ) + info += ", "; + + info += wxString::Format("%d images", numImages); + } + + Create(parent, desc, wxBitmap(image, wxBITMAP_SCREEN_DEPTH, scale), info); } MyImageFrame(wxFrame *parent, const wxString& desc, const wxBitmap& bitmap) @@ -137,7 +170,7 @@ private: bool Create(wxFrame *parent, const wxString& desc, const wxBitmap& bitmap, - int numImages = 1) + wxString info = wxString()) { if ( !wxFrame::Create(parent, wxID_ANY, wxString::Format("Image from %s", desc), @@ -169,8 +202,7 @@ private: mbar->Check(ID_PAINT_BG, true); CreateStatusBar(2); - if ( numImages != 1 ) - SetStatusText(wxString::Format("%d images", numImages), 1); + SetStatusText(info, 1); SetClientSize(bitmap.GetWidth(), bitmap.GetHeight()); @@ -439,6 +471,41 @@ private: Refresh(); } + // This is a copy of protected wxImageHandler::GetResolutionFromOptions() + static wxImageResolution GetResolutionFromOptions(const wxImage& image, int* x, int* y) + { + wxCHECK_MSG(x && y, wxIMAGE_RESOLUTION_NONE, wxT("NULL pointer")); + + if ( image.HasOption(wxIMAGE_OPTION_RESOLUTIONX) && + image.HasOption(wxIMAGE_OPTION_RESOLUTIONY) ) + { + *x = image.GetOptionInt(wxIMAGE_OPTION_RESOLUTIONX); + *y = image.GetOptionInt(wxIMAGE_OPTION_RESOLUTIONY); + } + else if ( image.HasOption(wxIMAGE_OPTION_RESOLUTION) ) + { + *x = + *y = image.GetOptionInt(wxIMAGE_OPTION_RESOLUTION); + } + else // no resolution options specified + { + *x = + *y = 0; + + return wxIMAGE_RESOLUTION_NONE; + } + + // get the resolution unit too + int resUnit = image.GetOptionInt(wxIMAGE_OPTION_RESOLUTIONUNIT); + if ( !resUnit ) + { + // this is the default + resUnit = wxIMAGE_RESOLUTION_INCHES; + } + + return (wxImageResolution)resUnit; + } + wxBitmap m_bitmap; double m_zoom; @@ -631,7 +698,9 @@ enum ID_INFO, ID_SHOWRAW, ID_GRAPHICS, - ID_SHOWTHUMBNAIL + ID_SHOWTHUMBNAIL, + ID_COPY_IMAGE, + ID_PASTE_IMAGE }; wxIMPLEMENT_DYNAMIC_CLASS( MyFrame, wxFrame ); @@ -651,6 +720,8 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) #if wxUSE_CLIPBOARD EVT_MENU(wxID_COPY, MyFrame::OnCopy) EVT_MENU(wxID_PASTE, MyFrame::OnPaste) + EVT_MENU(ID_COPY_IMAGE, MyFrame::OnCopyImage) + EVT_MENU(ID_PASTE_IMAGE, MyFrame::OnPasteImage) #endif // wxUSE_CLIPBOARD EVT_UPDATE_UI(ID_NEW_HIDPI, MyFrame::OnUpdateNewFrameHiDPI) wxEND_EVENT_TABLE() @@ -686,8 +757,11 @@ MyFrame::MyFrame() #if wxUSE_CLIPBOARD wxMenu *menuClipboard = new wxMenu; - menuClipboard->Append(wxID_COPY, "&Copy test image\tCtrl-C"); - menuClipboard->Append(wxID_PASTE, "&Paste image\tCtrl-V"); + menuClipboard->Append(wxID_COPY, "&Copy test image as wxBitmap\tCtrl-C"); + menuClipboard->Append(wxID_PASTE, "&Paste image as wxBitmap\tCtrl-V"); + menuClipboard->AppendSeparator(); + menuClipboard->Append(ID_COPY_IMAGE, "Copy image as wxImage"); + menuClipboard->Append(ID_PASTE_IMAGE, "Paste image as wxImage"); menu_bar->Append(menuClipboard, "&Clipboard"); #endif // wxUSE_CLIPBOARD @@ -945,6 +1019,38 @@ void MyFrame::OnPaste(wxCommandEvent& WXUNUSED(event)) wxTheClipboard->Close(); } +void MyFrame::OnCopyImage(wxCommandEvent& WXUNUSED(evt)) +{ + wxImage img; + wxString filename = LoadUserImage(img); + if ( filename.empty() ) + return; + + wxImageDataObject* dobjImage = new wxImageDataObject; + dobjImage->SetImage(img); + + wxClipboardLocker clipOpener; + if ( !wxTheClipboard->SetData(dobjImage) ) + { + wxLogError("Failed to copy wxImage to clipboard"); + } +} + +void MyFrame::OnPasteImage(wxCommandEvent& WXUNUSED(evt)) +{ + wxImageDataObject dobjImage; + + wxClipboardLocker clipOpener; + if ( !wxTheClipboard->GetData(dobjImage) ) + { + wxLogMessage("No wxImage data in the clipboard"); + } + else + { + new MyImageFrame(this, "Clipboard (wxImage)", dobjImage.GetImage()); + } +} + #endif // wxUSE_CLIPBOARD void MyFrame::OnThumbnail( wxCommandEvent &WXUNUSED(event) ) diff --git a/src/common/dobjcmn.cpp b/src/common/dobjcmn.cpp index d963ac4cbe..20ce23217d 100644 --- a/src/common/dobjcmn.cpp +++ b/src/common/dobjcmn.cpp @@ -20,6 +20,7 @@ #include "wx/app.h" #endif +#include "wx/mstream.h" #include "wx/textbuf.h" // ---------------------------------------------------------------------------- @@ -620,6 +621,59 @@ bool wxCustomDataObject::SetData(size_t size, const void *buf) return true; } +// ---------------------------------------------------------------------------- +// wxImageDataObject +// ---------------------------------------------------------------------------- + +#if defined(__WXMSW__) +#define wxIMAGE_FORMAT_DATA wxDF_PNG +#define wxIMAGE_FORMAT_BITMAP_TYPE wxBITMAP_TYPE_PNG +#define wxIMAGE_FORMAT_NAME "PNG" +#elif defined(__WXGTK__) +#define wxIMAGE_FORMAT_DATA wxDF_BITMAP +#define wxIMAGE_FORMAT_BITMAP_TYPE wxBITMAP_TYPE_PNG +#define wxIMAGE_FORMAT_NAME "PNG" +#elif defined(__WXOSX__) +#define wxIMAGE_FORMAT_DATA wxDF_BITMAP +#define wxIMAGE_FORMAT_BITMAP_TYPE wxBITMAP_TYPE_TIFF +#define wxIMAGE_FORMAT_NAME "TIFF" +#else +#define wxIMAGE_FORMAT_DATA wxDF_BITMAP +#define wxIMAGE_FORMAT_BITMAP_TYPE wxBITMAP_TYPE_PNG +#define wxIMAGE_FORMAT_NAME "PNG" +#endif + +wxImageDataObject::wxImageDataObject(const wxImage& image) + : wxCustomDataObject(wxIMAGE_FORMAT_DATA) +{ + if ( image.IsOk() ) + { + SetImage(image); + } +} + +void wxImageDataObject::SetImage(const wxImage& image) +{ + wxCHECK_RET(wxImage::FindHandler(wxIMAGE_FORMAT_BITMAP_TYPE) != NULL, + wxIMAGE_FORMAT_NAME " image handler must be installed to use clipboard with image"); + + wxMemoryOutputStream mem; + image.SaveFile(mem, wxIMAGE_FORMAT_BITMAP_TYPE); + + SetData(mem.GetLength(), mem.GetOutputStreamBuffer()->GetBufferStart()); +} + +wxImage wxImageDataObject::GetImage() const +{ + wxCHECK_MSG(wxImage::FindHandler(wxIMAGE_FORMAT_BITMAP_TYPE) != NULL, wxNullImage, + wxIMAGE_FORMAT_NAME " image handler must be installed to use clipboard with image"); + + wxMemoryInputStream mem(GetData(), GetSize()); + wxImage image; + image.LoadFile(mem, wxIMAGE_FORMAT_BITMAP_TYPE); + return image; +} + // ============================================================================ // some common dnd related code // ============================================================================ diff --git a/src/msw/clipbrd.cpp b/src/msw/clipbrd.cpp index 9df2a53c38..f349449fbd 100644 --- a/src/msw/clipbrd.cpp +++ b/src/msw/clipbrd.cpp @@ -73,6 +73,7 @@ static bool gs_wxClipboardIsOpen = false; static int gs_htmlcfid = 0; +static int gs_pngcfid = 0; bool wxOpenClipboard() { @@ -136,6 +137,8 @@ bool wxIsClipboardFormatAvailable(wxDataFormat dataFormat) wxDataFormat::NativeFormat cf = dataFormat.GetFormatId(); if (cf == wxDF_HTML) cf = gs_htmlcfid; + else if ( cf == wxDF_PNG ) + cf = gs_pngcfid; if ( ::IsClipboardFormatAvailable(cf) ) { @@ -162,6 +165,15 @@ bool wxIsClipboardFormatAvailable(wxDataFormat dataFormat) #if !wxUSE_OLE_CLIPBOARD +namespace +{ +struct wxRawImageData +{ + size_t m_size; + void* m_data; +}; +} + bool wxSetClipboardData(wxDataFormat dataFormat, const void *data, int width, int height) @@ -414,6 +426,16 @@ bool wxSetClipboardData(wxDataFormat dataFormat, delete [] buf; break; } + + case wxDF_PNG: + { + const wxRawImageData* imgData = reinterpret_cast(data); + + GlobalPtr hImage(imgData->m_size, GMEM_MOVEABLE | GMEM_DDESHARE); + memcpy(GlobalPtrLock(hImage).Get(), imgData->m_data, imgData->m_size); + handle = ::SetClipboardData(gs_pngcfid, hImage); + break; + } } if ( handle == 0 ) @@ -515,9 +537,11 @@ bool wxClipboard::Flush() bool wxClipboard::Open() { - // Get clipboard id for HTML format... + // Get clipboard id for HTML and PNG formats... if(!gs_htmlcfid) gs_htmlcfid = RegisterClipboardFormat(wxT("HTML Format")); + if ( !gs_pngcfid ) + gs_pngcfid = ::RegisterClipboardFormat(wxT("PNG")); // OLE opens clipboard for us m_isOpened = true; @@ -636,6 +660,16 @@ bool wxClipboard::AddData( wxDataObject *data ) } break; + case wxDF_PNG: + { + wxCustomDataObject* imageDataObject = reinterpret_cast(data); + wxRawImageData imgData; + imgData.m_size = imageDataObject->GetDataSize(); + imgData.m_data = imageDataObject->GetData(); + bRet = wxSetClipboardData(format, &imgData); + } + break; + #if wxUSE_METAFILE case wxDF_METAFILE: { @@ -768,6 +802,8 @@ bool wxClipboard::GetData( wxDataObject& data ) if (cf == wxDF_HTML) cf = gs_htmlcfid; + else if ( cf == wxDF_PNG ) + cf = gs_pngcfid; // if the format is not available, try the next one // this test includes implicit / sythetic formats if ( !::IsClipboardFormatAvailable(cf) ) diff --git a/src/msw/ole/dataobj.cpp b/src/msw/ole/dataobj.cpp index bb55833eca..f323893bd4 100644 --- a/src/msw/ole/dataobj.cpp +++ b/src/msw/ole/dataobj.cpp @@ -64,15 +64,15 @@ namespace { -wxDataFormat HtmlFormatFixup(wxDataFormat format) +wxDataFormat NonStandardFormatsFixup(wxDataFormat format) { - // Since the HTML format is dynamically registered, the wxDF_HTML - // format does not match the native constant in the way other formats do, + // Since the HTML and PNG formats are dynamically registered, the wxDF_HTML and wxDF_PNG + // formats do not match the native constants in the way other formats do, // so for the format checks below to work, we must change the native - // id to the wxDF_HTML constant. + // id to the wxDF_HTML or wxDF_PNG constant. // // But skip this for the standard constants which are never going to match - // wxDF_HTML anyhow. + // wxDF_HTML or wxDF_PNG anyhow. if ( !format.IsStandard() ) { wxChar szBuf[256]; @@ -80,6 +80,8 @@ wxDataFormat HtmlFormatFixup(wxDataFormat format) { if ( wxStrcmp(szBuf, wxT("HTML Format")) == 0 ) format = wxDF_HTML; + else if ( wxStrcmp(szBuf, wxT("PNG")) == 0 ) + format = wxDF_PNG; } } @@ -347,7 +349,7 @@ wxIDataObject::SaveSystemData(FORMATETC *pformatetc, bool wxDataFormat::operator==(wxDataFormatId format) const { - return HtmlFormatFixup(*this).m_format == (NativeFormat)format; + return NonStandardFormatsFixup(*this).m_format == (NativeFormat)format; } bool wxDataFormat::operator!=(wxDataFormatId format) const @@ -357,7 +359,7 @@ bool wxDataFormat::operator!=(wxDataFormatId format) const bool wxDataFormat::operator==(const wxDataFormat& format) const { - return HtmlFormatFixup(*this).m_format == HtmlFormatFixup(format).m_format; + return NonStandardFormatsFixup(*this).m_format == NonStandardFormatsFixup(format).m_format; } bool wxDataFormat::operator!=(const wxDataFormat& format) const @@ -367,7 +369,7 @@ bool wxDataFormat::operator!=(const wxDataFormat& format) const bool wxDataFormat::operator==(NativeFormat format) const { - return HtmlFormatFixup(*this).m_format == format; + return NonStandardFormatsFixup(*this).m_format == format; } bool wxDataFormat::operator!=(NativeFormat format) const @@ -422,10 +424,12 @@ wxIEnumFORMATETC::wxIEnumFORMATETC(const wxDataFormat *formats, ULONG nCount) m_nCount = nCount; m_formats = new CLIPFORMAT[nCount]; for ( ULONG n = 0; n < nCount; n++ ) { - if (formats[n].GetFormatId() != wxDF_HTML) - m_formats[n] = formats[n].GetFormatId(); - else + if ( formats[n].GetFormatId() == wxDF_HTML ) m_formats[n] = ::RegisterClipboardFormat(wxT("HTML Format")); + else if ( formats[n].GetFormatId() == wxDF_PNG ) + m_formats[n] = ::RegisterClipboardFormat(wxT("PNG")); + else + m_formats[n] = formats[n].GetFormatId(); } } @@ -540,7 +544,7 @@ STDMETHODIMP wxIDataObject::GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium) // for the bitmaps and metafiles we use the handles instead of global memory // to pass the data wxDataFormat format = (wxDataFormat::NativeFormat)pformatetcIn->cfFormat; - format = HtmlFormatFixup(format); + format = NonStandardFormatsFixup(format); // is this system data? if ( GetSystemData(format, pmedium) ) @@ -690,7 +694,7 @@ STDMETHODIMP wxIDataObject::SetData(FORMATETC *pformatetc, { wxDataFormat format = pformatetc->cfFormat; - format = HtmlFormatFixup(format); + format = NonStandardFormatsFixup(format); // check if this format is supported if ( !m_pDataObject->IsSupported(format, wxDataObject::Set) ) { @@ -737,6 +741,9 @@ STDMETHODIMP wxIDataObject::SetData(FORMATETC *pformatetc, size = sizeof(METAFILEPICT); break; + case wxDF_PNG: + wxFALLTHROUGH; + default: size = ptr.GetSize(); @@ -816,7 +823,7 @@ STDMETHODIMP wxIDataObject::QueryGetData(FORMATETC *pformatetc) // and now check the type of data requested wxDataFormat format = pformatetc->cfFormat; - format = HtmlFormatFixup(format); + format = NonStandardFormatsFixup(format); if ( m_pDataObject->IsSupportedFormat(format) ) { wxLogTrace(wxTRACE_OleCalls, wxT("wxIDataObject::QueryGetData: %s ok"), diff --git a/tests/image/image.cpp b/tests/image/image.cpp index 43d493beb9..bebc9921ee 100644 --- a/tests/image/image.cpp +++ b/tests/image/image.cpp @@ -26,6 +26,8 @@ #include "wx/mstream.h" #include "wx/zstream.h" #include "wx/wfstream.h" +#include "wx/clipbrd.h" +#include "wx/dataobj.h" #include "testimage.h" @@ -1942,6 +1944,32 @@ TEST_CASE("wxImage::RGBtoHSV", "[image][rgb][hsv]") } } +TEST_CASE("wxImage::Clipboard", "[image][clipboard]") +{ +#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ + wxInitAllImageHandlers(); + + wxImage imgOriginal; + REQUIRE(imgOriginal.LoadFile("horse.png") == true); + + wxImageDataObject* dobj1 = new wxImageDataObject(imgOriginal); + { + wxClipboardLocker lockClip; + REQUIRE(wxTheClipboard->SetData(dobj1) == true); + } + + wxImageDataObject dobj2; + { + wxClipboardLocker lockClip; + REQUIRE(wxTheClipboard->GetData(dobj2) == true); + } + wxImage imgRetrieved = dobj2.GetImage(); + REQUIRE(imgRetrieved.IsOk()); + + CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_PNG), imgRetrieved, 0, &imgOriginal); +#endif // wxUSE_CLIPBOARD && wxUSE_DATAOBJ +} + /* TODO: add lots of more tests to wxImage functions */