14.23: WebP import
This commit is contained in:
13 files changed
+93
-16
No files matched your search
@@ -201,6 +201,7 @@ Platform adaptations, each forced by a missing Qt analog:
|
||||
- Filter › Scanlines (14.19, Swift 1.4.9: `06c3a22`, `1187736`, `cdde327`): Scanlines leaves Dither for a filter of its own. `kernels/DitherPixels.c/.h` are `cdde327`'s, with 13.17's adaptation carried on: Swift's GCD blocks become static band functions with context structs (`scanlines_tone_band`, `line_scan_band`, `blur_band`, `spread_band`, `column_band`, `split_band`), each failure flag an `atomic_int`, every band through `dither_bands`; the arithmetic is Swift's word for word, and `dither_glow`'s new easing near white came with it. `ScanlinesTests::theKernelDrawsSwiftsOwnBytes` holds that: Swift's own file, built unchanged with clang's blocks and libdispatch in the `swift:6.0.3` image, drew five scenes (37 by 53 and 70 by 300, every setting, both directions of Displace, 32 bands of columns, lines and rows) and a glow, and the twin's bytes hash the same (FNV-1a). `Document/Scanlines.h/.cpp` twin Swift's file: `ScanlinesSettings` with Swift's fields, defaults (glow 0 now, thickness 70, smoothness 50) and clamps, Line Spacing and Color Split rounded, `apply` handing the kernel the normalized settings and Two Colors' bytes (black on white otherwise), then 13.17's `glowing` moved here unchanged. `DitherStyle` loses `scanlines` (`usesPixelSize` is ASCII alone, `drawsMarks` every style without tones) and `DitherSettings` its four fields. `FilterKind::scanlines` ("Scanlines") sits after Dither in Swift's order, so the Filter menu lists it there; it previews whole, as Dither does. Its two colours reuse Dither's picker (`openDitherColorPicker`, `setDitherColor`, Swift's), choosing the edit's own settings, and the titles drop "Dither" (`Color Picker (Light Color)`, `(Dark Color)`). The sheet's `scanlines` (`UI/FilterSheet+Scanlines.cpp`) is Swift's `scanlinesControls`: twelve rows with Swift's ranges, units and help, then the Colors menu and swatches, which `colorChoices` builds for Dither too (the screen's and the lines' colour help for Scanlines). Equivalent mutant: `glowing` run at glow 0 (it adds nothing). Tests: `ScanlinesTests` (Swift's eight, Swift's own bytes, each setting reaching its field, Two Colors rounded, every clamp through `FilterSettings` too, clear pixels, the eased glow against a full-size blur), `ScanlinesSheetTests` (the menu place and a commit, the sheet's rows, ranges and fields, both pickers), the order in `FilterModelTests`, `FinishingFilterTests`, `DitherSheetTests` and `CompositorMenusTests`, the acceptance run's Scanlines row through the menu.
|
||||
- The Navigator (14.20, Swift 1.4.9: `6490bb3`, `3e09948`; `3c6cf5e`'s conflict resolved toward the minimap, which supersedes the panel): `Rendering/NavigatorGeometry.h/.cpp` twin Swift's file: `NavigatorGeometry` (the document fitted and centred in a box, `thumbnailRect`, `documentPoint` held to the document's edges), and `CanvasViewport::visibleDocumentRect` and `centerView`, declared in `CanvasViewport.h` and defined there, as Swift's extension; `centerView` goes through `translate`, so the view stops following Fit. Swift's `min` and `abs` in `visibleDocumentRect` are dead (the viewport never flips) and left out. `EditorSession::navigatorImage` (`EditorSession+Navigator.cpp`, its fragment `EditorSession+Navigator.h`) twins Swift's: the document drawn at the picture's size, never enlarged, each side rounded, a pixel at least, every layer and placed mask scaled by its axis, blur radius and motion distance by the width's, through a bare session's `drawLiveComposite` so an edit in progress stays out; a context that cannot be allocated, or a painted layer that cannot flatten while it draws, logs on `omaphoto.rendering` and gives no picture, where Swift's `try?` is silent (`NavigatorFailureTests`, a binary of its own: a 4000 by 3000 painted layer under 8 MB of room, the minimap shown without a picture). Swift's medium interpolation on the context is dead here (`LayerRenderer` sets each layer's own) and left out. `UI/NavigatorMinimap` twins Swift's view: a `QWidget` in `ContentView`'s canvas overlay (the mask badge's layout, its margins 12, top right), following the session's `changed()` itself: shown while View › Navigator holds (`navigator.visible` in `QSettings`, Swift's `AppStorage`, off at first), Canvas Only is off, a document is open and the zoom is 300% or more; hidden it drops its picture, key and wait, as SwiftUI drops the view. Its size is `sizeFor` (Swift's `size(for:)`: `QWidget::size` takes the name), fitted inside 126 by 90 and rounded; it fills gray 0.11, draws the 300-pixel picture smoothly over its whole box, the view's box in the accent 2.5 wide inside its rect (three points at least), clipped to a rounded rect of 4, then a 25% white rim. Swift's `task(id:)` is `RenderKey` (the document's id, size and layers and `brushRevision`, so a selection or guide redraws nothing): the first picture renders at once, each later key after a 250 ms single-shot precise `navigatorWait`. A left press or drag centres the view on the point (`centerView`, then `notify`), the arrow its cursor over the canvas's. Known difference: Swift's shadow (radius 6) has no twin, since a widget paints only inside itself. View › Navigator (300% and above) is a checkable entry before Pixel Grid that writes the setting and notifies; the command palette finds it. Tests: `NavigatorTests` (Swift's nine; the geometry's edge cases, both axes and a placed mask scaled, a layer and its mask off the origin, a streak shrunk at its edge, both blurs by the width's factor where the two factors differ, centring at a backing scale of two, the wait restarted by a later edit, a transform in progress left out, the minimap's showing, waiting, clicks, cursor, box, gray and rim), `ViewMenuTests::theNavigatorEntryShowsTheMinimap` (the entry's place and words, the minimap 12 points in at the canvas's top right), the View menu's count in `CompositorMenusTests`. Equivalent mutants: none found.
|
||||
- Effects, text and shapes kept (14.22, Swift 1.4.9: `94e90bd`): `ImageLayer(record, asset, mask)` loads its live shape and text (`LayerShape::loaded`, `LayerText::loaded`), Swift's `ProjectSnapshot.documentLayers`, so `installProject` and `applyDocumentSize` (Canvas Size, Image Size, Crop, Trim) build the same layers; the canvas resizer carries effects, and Image Size scales them by the root of its two factors through `LayerEffects::scaled` (stroke, glows and blurs to 500, distances to 5,000, Photoshop's Scale Styles) while its resampling still flattens text and shapes, and a size that changes only the resolution keeps all three. Hue/Saturation, Levels, Invert and a floating merge keep the layer's effects. `docs/project-format.md` says so. Tests: `LayerEffectsSurviveTests` (Swift's five sizing cases; a stretch's mean factor and the ranges), `LayerEffectsTests::rebuildsAndResizesKeepThem` (the four rebuilds, both resizers), the shape and text halves in `ShapeLayerTests` and `TypeStoreTests`.
|
||||
- WebP (14.23, Swift 1.4.9: `5724099`): `ImageImporter` takes Qt's `webp` format beside JPEG, PNG and TIFF (`qt6-image-formats-plugins` carries it, as it carries TIFF; Fedora's `qt6-qtimageformats`, Arch's `qt6-imageformats` and nixpkgs' `qtimageformats` too), through the same reading, sRGB conversion and thumbnail; a dropped picture prefers WebP after TIFF and before Photoshop's, Swift's order; the unsupported and drop messages name WebP; the importer's filter offers `*.webp` and the desktop entry `image/webp`. Tests: `WebPImportTests` (Swift's lossless and lossy pictures: size, sRGB, the clear half, the budget, a drop opening a document; the drop order), the words in their files.
|
||||
- Drops between projects: `ProjectWorkspace::draggedLayerSource` finds the tab whose document holds the dragged id (Swift reads the drag pasteboard; the twin reads the drag's `QMimeData`, which every drop target has), `canReceiveDrag(data, destination)` refuses only a layer's own tab, and `receiveProviders` copies several ids one after another, as Swift awaits each provider, so a two-row drop on the New button opens two tabs as Swift's does. `ContentView` takes such a drop over its whole area (`setWorkspace` hands it the workspace; `acceptsDrop` keeps Swift's guards: busy, the sheet, the importer, a rename; `levels` arrives with 9.x), shows the 3-point accent ring inside the canvas while targeted and hands the drop's document point on when it fell on the canvas. `ProjectTabButton` takes drops for its tab (accent fill and rim, "Add to <title>"), and `NewCanvasButton`, the toolbar's New canvas button with the action as its default, for a fresh tab ("Open in a new project tab"), both refusing an Alt drag, which duplicates in the panel; Swift's transient `NewTabDropSlot` in the strip has no twin, the New canvas button is `NewProjectDropTarget` already. Files and pictures arrived with 10.4.
|
||||
- `CanvasThumbnail` draws a layer or a mask inside the canvas's box at `backingScale` 2 (the panel's pixmaps carry that ratio); a mask's ground is `LayerMask::background`, white or black by its edge's majority, as the canvas treats it (Swift's `edgeTone` mean went with 12.12b). `LayerIcons` draws the panel's SF Symbols as paths, in one palette colour, like `ToolIcons`. `BlendModePicker` is a `QComboBox` whose popup previews the highlighted mode through `previewBlendMode` for the layer it opened on (Qt calls `hidePopup` before `activated`, so the preview, the target and the highlight are cleared from the event loop, after a choice had its chance) and refuses a choice once another layer is active. `LayerAppearanceControls` is Swift's blend and opacity row: the slider's press and release are one opacity drag (the release is watched application-wide while the slider is down, so a popup opened mid-drag cannot keep the step open), the percent field applies once through its `FocusOut`, which Return and Escape cause (Swift's `releasesFocusOnCommit`), and steps with Up and Down (Swift's `isFinite` test and the steps' clamp are left out: `setLayerOpacity` refuses a value that is no finite number and clamps the rest), and another active layer makes the controls anew, as Swift's `.id` does.
|
||||
- `ProjectWorkspaceView` is the `QMainWindow`: the front tab's `ContentView` as its central widget, made anew when another tab comes to the front, as SwiftUI's `.id` rebuilds it, and disabled while the workspace manages; the window holds the shown tab (`std::shared_ptr`) until the old editor is deleted, as Swift's views hold their session, so a replaced or closed tab's session never ends under its editor (`ProjectWorkspace::tab` is public for this); a `QToolBar` with the New canvas action, the `ProjectTabStrip` (which takes the toolbar's free width) and the four zoom actions, since SwiftUI's `.toolbar` modifier lives in `ContentView` but the toolbar belongs to the window. Its actions follow the front session's `changed()`, and on that signal the window syncs every tab's button as well, because a busy front tab dims the other tabs' buttons, which their own sessions' signals do not reach; the strip syncs its buttons on the workspace's signal, each button on its own session's. The window's title is the front tab's, with Qt's `[*]` placeholder, so `setWindowModified` can show the mark that `isDocumentEdited` shows; `setWindowFilePath` carries `representedURL`. The editor's own minimum size is cleared inside the window, which owns the 800 × 520 minimum for the whole, toolbar included. Closing the window ignores the event and asks the workspace (`closeWindow`), which confirms tab by tab and then closes the window again with the property `closeConfirmed` set, since the workspace may not include `UI/` headers; a confirmed close is accepted and the mark cleared, so a window shown again asks again. `ProjectTabStrip` is a plain `QWidget` whose buttons are kept for the tabs that stay, made for new tabs and deleted for tabs that went, and placed by hand from `projectTabOverflow` (13.14): every button at its own size hint, so no title is squeezed below Swift's 35 points, laid out again when the strip is resized, the workspace changes or any tab's session changes; it fills no background, so the toolbar shows through (a stylesheet would do the same but freezes the widgets' palette, so a theme switch would pass them by); the unsaved dot is a character before the title. Files named on the command line go to `ProjectWorkspace::receive`, as Finder's Open With reaches the delegate. Layer drops on tabs and the New canvas button are 4.7's; file and picture drops arrived with 10.4.
|
||||
|
||||
@@ -16,7 +16,7 @@ It is a Linux port of [Compositor](https://github.com/robbietilton/Compositor) b
|
||||
- Filters: Gaussian and motion blur, noise, lens correction, Vignette, Bloom / Glow, Tonal Contrast, Dither, Scanlines, Content-Aware Fill and Remove Background (U²-Net on ONNX Runtime, offline)
|
||||
- Last Filter (Ctrl+Meta+F) runs the last filter again with the same settings
|
||||
- Crop, trim, canvas size, image size; PNG, JPEG and one-page PDF export, scaled if you like, with a zoomable preview (File › Export As…); New Canvas presets for screens and social formats
|
||||
- Imports JPEG, PNG, TIFF, HEIC, SVG, Photoshop files (PSD and PSB) and camera RAW
|
||||
- Imports JPEG, PNG, TIFF, HEIC, WebP, SVG, Photoshop files (PSD and PSB) and camera RAW
|
||||
- A Navigator minimap in the canvas's corner from 300% zoom: the whole document in small with a box around the view; click or drag to move there (View › Navigator)
|
||||
- Open Recent, and a Keyboard Shortcuts window
|
||||
- Search Commands (Ctrl+F): find every menu command and tool by name and run it with Return
|
||||
|
||||
@@ -7,5 +7,5 @@ Exec=omaphoto %F
|
||||
Icon=io.github.ZacharyZhang_NY.OmaPhoto
|
||||
Terminal=false
|
||||
Categories=Graphics;2DGraphics;RasterGraphics;
|
||||
MimeType=application/x-compositor-project;image/png;image/jpeg;image/heic;image/tiff;image/vnd.adobe.photoshop;image/x-photoshop-large-image;image/svg+xml;image/svg+xml-compressed;image/x-dcraw;
|
||||
MimeType=application/x-compositor-project;image/png;image/jpeg;image/heic;image/tiff;image/webp;image/vnd.adobe.photoshop;image/x-photoshop-large-image;image/svg+xml;image/svg+xml-compressed;image/x-dcraw;
|
||||
StartupWMClass=OmaPhoto
|
||||
@@ -100,7 +100,7 @@ void ContentView::showImporter()
|
||||
m_importer = new QFileDialog(this);
|
||||
m_importer->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m_importer->setFileMode(QFileDialog::ExistingFiles);
|
||||
m_importer->setNameFilter(QStringLiteral("Images (*.jpg *.jpeg *.png *.heic *.tif *.tiff *.psd *.psb *.svg *.svgz %1)").arg(RawImporter::globs().join(' ')));
|
||||
m_importer->setNameFilter(QStringLiteral("Images (*.jpg *.jpeg *.png *.heic *.tif *.tiff *.webp *.psd *.psb *.svg *.svgz %1)").arg(RawImporter::globs().join(' ')));
|
||||
connect(m_importer, &QDialog::finished, this, [this, panel = m_importer.data()](int result) {
|
||||
// Ours no longer: the flag must not reject it again.
|
||||
if (m_importer != panel)
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
// Swift's order: PNG, JPEG, HEIC, TIFF, PSD, PSB, RAW, any.
|
||||
// Swift's order: PNG, JPEG, HEIC, TIFF, WebP, Photoshop, RAW, any.
|
||||
QStringList pictureFormats(const QMimeData &data)
|
||||
{
|
||||
QStringList formats;
|
||||
for (const QString &format : {QStringLiteral("image/png"), QStringLiteral("image/jpeg"), QStringLiteral("image/heic"), QStringLiteral("image/tiff"),
|
||||
QStringLiteral("image/vnd.adobe.photoshop"), QStringLiteral("image/x-photoshop-large-image")})
|
||||
QStringLiteral("image/webp"), QStringLiteral("image/vnd.adobe.photoshop"), QStringLiteral("image/x-photoshop-large-image")})
|
||||
if (data.hasFormat(format))
|
||||
formats << format;
|
||||
for (const QString &format : data.formats())
|
||||
@@ -102,7 +102,7 @@ void ImageFileDrop::importProviders(const QMimeData &data, EditorSession &sessio
|
||||
}
|
||||
const auto finished = [&session, unreadable, done] {
|
||||
if (unreadable) {
|
||||
const QString message = QStringLiteral("Some dropped items couldn’t be read. Drag JPEG, PNG, HEIC, TIFF, or Photoshop (PSD) files from the file manager.");
|
||||
const QString message = QStringLiteral("Some dropped items couldn’t be read. Drag JPEG, PNG, HEIC, TIFF, WebP, or Photoshop (PSD) files from the file manager.");
|
||||
session.setImportError(session.importError() ? *session.importError() + QStringLiteral("\n\n") + message : message);
|
||||
}
|
||||
if (done)
|
||||
|
||||
@@ -23,7 +23,7 @@ QString description(ImageImportError::Kind kind)
|
||||
case ImageImportError::Kind::unreadable:
|
||||
return QStringLiteral("The image could not be read. It may be damaged or unavailable.");
|
||||
case ImageImportError::Kind::unsupported:
|
||||
return QStringLiteral("Choose a JPEG, PNG, HEIC, TIFF, or Photoshop (PSD) file.");
|
||||
return QStringLiteral("Choose a JPEG, PNG, HEIC, TIFF, WebP, or Photoshop (PSD) file.");
|
||||
case ImageImportError::Kind::tooLarge:
|
||||
return QStringLiteral("This import exceeds the current %1-megapixel document budget or %2-pixel side limit.").arg(DocumentLimits::documentBudgetMegapixels()).arg(DocumentLimits::maxSideText());
|
||||
case ImageImportError::Kind::whiteBalance:
|
||||
@@ -244,7 +244,7 @@ ImportedImage ImageImporter::decode(const QString &path, qint64 remainingPixels,
|
||||
refuse(ImageImportError::Kind::unsupported, path, QStringLiteral("ISO media that is not HEIC"));
|
||||
if (!heic && format.isEmpty())
|
||||
refuse(ImageImportError::Kind::unreadable, path, QStringLiteral("no image format is recognised"));
|
||||
if (!heic && format != "jpeg" && format != "png" && format != "tiff")
|
||||
if (!heic && format != "jpeg" && format != "png" && format != "tiff" && format != "webp")
|
||||
refuse(ImageImportError::Kind::unsupported, path, QString::fromLatin1(format));
|
||||
// Qt's own turning drops the colour space: turn afterwards.
|
||||
QImageIOHandler::Transformations orientation = QImageIOHandler::TransformationNone;
|
||||
|
||||
@@ -323,7 +323,7 @@ void ContentViewTests::theImporterPicksFilesAndClearsItsFlag()
|
||||
QTest::qWait(50);
|
||||
QCOMPARE(int(session.document().value().layers.size()), 2);
|
||||
desk.typeBeforeCancel.clear();
|
||||
const QString panel = "panel|Open|open|file|||1 many Images (*.jpg *.jpeg *.png *.heic *.tif *.tiff *.psd *.psb *.svg *.svgz " + RawImporter::globs().join(" ") + ")";
|
||||
const QString panel = "panel|Open|open|file|||1 many Images (*.jpg *.jpeg *.png *.heic *.tif *.tiff *.webp *.psd *.psb *.svg *.svgz " + RawImporter::globs().join(" ") + ")";
|
||||
QCOMPARE(desk.seen.mid(0, 2), (QStringList{panel, panel}));
|
||||
QCOMPARE(desk.seen.size(), 3);
|
||||
// The flag cleared from elsewhere takes the panel down.
|
||||
|
||||
@@ -71,7 +71,7 @@ void DesktopIntegrationTests::theEntryOpensSwiftsDocumentTypes()
|
||||
QCOMPARE(keys.value("Terminal"), QString("false"));
|
||||
QVERIFY(keys.value("Categories").split(QLatin1Char(';')).contains("Graphics"));
|
||||
// Swift's two document types: its projects, then four pictures.
|
||||
QCOMPARE(keys.value("MimeType"), QString("application/x-compositor-project;image/png;image/jpeg;image/heic;image/tiff;image/vnd.adobe.photoshop;image/x-photoshop-large-image;image/svg+xml;image/svg+xml-compressed;image/x-dcraw;"));
|
||||
QCOMPARE(keys.value("MimeType"), QString("application/x-compositor-project;image/png;image/jpeg;image/heic;image/tiff;image/webp;image/vnd.adobe.photoshop;image/x-photoshop-large-image;image/svg+xml;image/svg+xml-compressed;image/x-dcraw;"));
|
||||
// Each SVG suffix's type as the system names it.
|
||||
for (const char *name : {"icon.svg", "icon.svgz"}) {
|
||||
const QString type = QMimeDatabase().mimeTypeForFile(QString::fromLatin1(name), QMimeDatabase::MatchExtension).name();
|
||||
|
||||
@@ -173,7 +173,7 @@ void ImageFileDropTests::aPictureIsCopiedToAFileFirst()
|
||||
|
||||
void ImageFileDropTests::whatCannotBeReadIsSaid()
|
||||
{
|
||||
const QString said = "Some dropped items couldn’t be read. Drag JPEG, PNG, HEIC, TIFF, or Photoshop (PSD) files from the file manager.";
|
||||
const QString said = "Some dropped items couldn’t be read. Drag JPEG, PNG, HEIC, TIFF, WebP, or Photoshop (PSD) files from the file manager.";
|
||||
EditorSession session;
|
||||
bool done = false;
|
||||
std::unique_ptr<QMimeData> link(files({}, {QUrl("https://example.com/page")}));
|
||||
@@ -354,7 +354,7 @@ void ImageFileDropTests::aPictureThatCannotBeKeptIsUnreadable()
|
||||
ImageFileDrop::importProviders(data, session, std::nullopt, nullptr, std::nullopt, [&done] { done = true; });
|
||||
QCOMPARE(setrlimit(RLIMIT_FSIZE, &room), 0);
|
||||
QTRY_VERIFY(done);
|
||||
QCOMPARE(session.importError(), std::optional<QString>("Some dropped items couldn’t be read. Drag JPEG, PNG, HEIC, TIFF, or Photoshop (PSD) files from the file manager."));
|
||||
QCOMPARE(session.importError(), std::optional<QString>("Some dropped items couldn’t be read. Drag JPEG, PNG, HEIC, TIFF, WebP, or Photoshop (PSD) files from the file manager."));
|
||||
QVERIFY(!session.document());
|
||||
QCOMPARE(dropped(), before);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// The session cases of Swift's ImageImportTests, and the queue.
|
||||
namespace {
|
||||
const QString unreadable = QStringLiteral("The image could not be read. It may be damaged or unavailable.");
|
||||
const QString unsupported = QStringLiteral("Choose a JPEG, PNG, HEIC, TIFF, or Photoshop (PSD) file.");
|
||||
const QString unsupported = QStringLiteral("Choose a JPEG, PNG, HEIC, TIFF, WebP, or Photoshop (PSD) file.");
|
||||
const QString tooLarge = QStringLiteral("This import exceeds the current %1-megapixel document budget or 30,000-pixel side limit.").arg(DocumentLimits::documentBudgetMegapixels());
|
||||
|
||||
// A 64 by 32 PNG, as Swift's fixture is.
|
||||
|
||||
@@ -385,8 +385,7 @@ void ImageImportTests::limitsAndInvalidFiles()
|
||||
const QByteArray gif = QByteArray::fromHex("47494638396101000100800000000000ffffff21f90401000000002c00000000010001000002024401003b");
|
||||
const QByteArray avif = QByteArray::fromHex("0000001c667479706176696600000000617669666d6966316d696166");
|
||||
const QList<std::pair<QString, QByteArray>> unwanted{{"tiny.gif", gif}, {"tiny.avif", avif},
|
||||
{"fixture.bmp", encoded(block(QImage::Format_RGB888, red, Qt::white), "bmp")},
|
||||
{"fixture.webp", encoded(block(QImage::Format_RGB888, red, Qt::white), "webp")}};
|
||||
{"fixture.bmp", encoded(block(QImage::Format_RGB888, red, Qt::white), "bmp")}};
|
||||
for (const auto &[name, data] : unwanted) {
|
||||
QTest::ignoreMessage(QtWarningMsg, refusal);
|
||||
QVERIFY2(importError(written(folder, name, data)) == std::optional(ImageImportError::Kind::unsupported), qPrintable(name));
|
||||
@@ -460,7 +459,7 @@ void ImageImportTests::thumbnailsAreSmoothed()
|
||||
void ImageImportTests::errorsCarrySwiftsDescriptions()
|
||||
{
|
||||
QCOMPARE(QString(ImageImportError(ImageImportError::Kind::unreadable).what()), QString("The image could not be read. It may be damaged or unavailable."));
|
||||
QCOMPARE(QString(ImageImportError(ImageImportError::Kind::unsupported).what()), QString("Choose a JPEG, PNG, HEIC, TIFF, or Photoshop (PSD) file."));
|
||||
QCOMPARE(QString(ImageImportError(ImageImportError::Kind::unsupported).what()), QString("Choose a JPEG, PNG, HEIC, TIFF, WebP, or Photoshop (PSD) file."));
|
||||
QCOMPARE(QString(ImageImportError(ImageImportError::Kind::tooLarge).what()),
|
||||
QStringLiteral("This import exceeds the current %1-megapixel document budget or 30,000-pixel side limit.").arg(DocumentLimits::documentBudgetMegapixels()));
|
||||
QCOMPARE(ImageImportError(ImageImportError::Kind::tooLarge).kind, ImageImportError::Kind::tooLarge);
|
||||
|
||||
@@ -131,6 +131,7 @@ compositor_test(HighDpiCanvasTests)
|
||||
compositor_test(HistoryTests)
|
||||
compositor_test(ImageImportSessionTests)
|
||||
compositor_test(ImageImportTests)
|
||||
compositor_test(WebPImportTests)
|
||||
compositor_test(ImageSizeTests)
|
||||
compositor_test(JPEGExportTests)
|
||||
compositor_test(LayerAppearanceControlsTests)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "Document/EditorSession.h"
|
||||
#include "IO/ImageFileDrop.h"
|
||||
#include "IO/ImageImporter.h"
|
||||
#include <QColorSpace>
|
||||
#include <QMimeData>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
// Swift 1.4.9's WebP import (`5724099`).
|
||||
class WebPImportTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void webPImportPreservesTransparency_data();
|
||||
void webPImportPreservesTransparency();
|
||||
void aDroppedPictureTakesWebPAfterTIFF();
|
||||
};
|
||||
|
||||
void WebPImportTests::webPImportPreservesTransparency_data()
|
||||
{
|
||||
// 64 by 32: red left, clear right.
|
||||
QTest::addColumn<QByteArray>("encoded");
|
||||
QTest::newRow("lossless") << QByteArray("UklGRiYAAABXRUJQVlA4TBkAAAAvP8AHEA8Q8x/zHwyxYDJ/6RB6I/ofVfANAA==");
|
||||
QTest::newRow("lossy") << QByteArray("UklGRoYAAABXRUJQVlA4WAoAAAAQAAAAPwAAHwAAQUxQSBQAAAABDzD/ERFCLJjMXzqE3oj+RxV8A1ZQOCBMAAAA8AMAnQEqQAAgAD4xFolDIiEhFgQAIAMEsYBmu0A/AD8AADKMNAliwAD+96KX/9eiUqpf3sP/+Fmfwsz+Fmf/Cc2fv90UD4A2eDkAAA==");
|
||||
}
|
||||
|
||||
void WebPImportTests::webPImportPreservesTransparency()
|
||||
{
|
||||
QFETCH(QByteArray, encoded);
|
||||
const QByteArray bytes = QByteArray::fromBase64(encoded);
|
||||
QTemporaryDir folder;
|
||||
// The bytes decide, as everywhere: no suffix needed.
|
||||
const QString path = folder.filePath(QStringLiteral("picture"));
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly) && file.write(bytes) == bytes.size());
|
||||
file.close();
|
||||
const ImportedImage result = ImageImporter::decode(path);
|
||||
const QImage image = result.image();
|
||||
QCOMPARE(image.size(), QSize(64, 32));
|
||||
QCOMPARE(image.colorSpace(), QColorSpace(QColorSpace::SRgb));
|
||||
QCOMPARE(image.format(), QImage::Format_RGBA8888_Premultiplied);
|
||||
QVERIFY(result.thumbnail.width() <= 96 && result.thumbnail.height() <= 96);
|
||||
const uchar *row = image.constScanLine(0);
|
||||
QVERIFY(row[0] >= 250 && row[3] == 255);
|
||||
QCOMPARE(int(row[63 * 4 + 3]), 0);
|
||||
// Past its budget it is too large.
|
||||
QTest::ignoreMessage(QtWarningMsg, QRegularExpression("^cannot import "));
|
||||
try {
|
||||
ImageImporter::decode(path, 10);
|
||||
QFAIL("an over-budget WebP decoded");
|
||||
} catch (const ImageImportError &error) {
|
||||
QCOMPARE(error.kind, ImageImportError::Kind::tooLarge);
|
||||
}
|
||||
// A dropped WebP picture opens a document of its own.
|
||||
EditorSession session;
|
||||
QMimeData data;
|
||||
data.setData(QStringLiteral("image/webp"), bytes);
|
||||
bool done = false;
|
||||
ImageFileDrop::importProviders(data, session, std::nullopt, nullptr, std::nullopt, [&done] { done = true; });
|
||||
QTRY_VERIFY(done);
|
||||
QCOMPARE(session.document().value().size(), QSizeF(64, 32));
|
||||
QCOMPARE(session.document().value().layers.size(), size_t(1));
|
||||
QVERIFY(!session.importError());
|
||||
}
|
||||
|
||||
void WebPImportTests::aDroppedPictureTakesWebPAfterTIFF()
|
||||
{
|
||||
QMimeData data;
|
||||
data.setData(QStringLiteral("image/vnd.adobe.photoshop"), "psd");
|
||||
data.setData(QStringLiteral("image/webp"), "webp");
|
||||
QCOMPARE(ImageFileDrop::providers(data).front()->formats(), QStringList{"image/webp"});
|
||||
data.setData(QStringLiteral("image/tiff"), "tiff");
|
||||
QCOMPARE(ImageFileDrop::providers(data).front()->formats(), QStringList{"image/tiff"});
|
||||
}
|
||||
|
||||
QTEST_MAIN(WebPImportTests)
|
||||
#include "WebPImportTests.moc"
|
||||
Reference in new issue
Block a user