Skip to content

ImageDiff

tsx
<ImageDiff before={saved} after={rendered} />
<ImageDiff mode="editor" view="wipe" />
dart
ImageDiff(before: DiffineEncodedImage(saved), after: DiffineEncodedImage(rendered));
ImageDiff(mode: DiffineMode.editor, view: DiffineImageView.wipe, onChoose: pick);

The pictures

PropTypeDefaultWhat it is
mode'viewer' | 'editor''viewer'Whether the pictures are only looked at, or chosen as well.
beforeDiffineImageInputThe picture on the left.
afterDiffineImageInputThe picture on the right.
picturesreadonly DiffineImageInput[]Several pictures rather than two. Turns the list on.
baselinenumber0Which of them the rest are counted against.
picturesResultDiffImagesResultA comparison of the list already worked out.
onPicturesDiff(result: DiffImagesResult | null) => voidThe comparison of the list.
defaultBeforeDiffineImageInputWhat the left side starts with. Editor only.
defaultAfterDiffineImageInputWhat the right side starts with. Editor only.
onBeforeChange(value: File) => voidA picture was chosen for the left side.
onAfterChange(value: File) => voidA picture was chosen for the right side.
onDiff(result: DiffImageResult | null) => voidThe comparison, every time it is worked out again.
resultDiffImageResultA comparison already worked out. The pictures are still drawn.
diffDiffImageOptionsHow the two are compared. See diffImage.
maxPixelsnumber4000000How many pixels a picture is decoded at, at most.

DiffineImageInput is a picture or a picture with a name on it: Blob | ImageBitmap | DiffPixels, or { content, label } around one of those. A URL is not among them — fetching one is the application's to do, and what arrives here is what it already holds.

editor mode is the usual React pair. defaultBefore and defaultAfter leave the pictures to the component; before and after make them the application's, and onBeforeChange and onAfterChange are called either way.

ArgumentTypeDefaultWhat it is
modeDiffineModeDiffineMode.viewerWhether the pictures are only looked at, or chosen as well.
beforeDiffineImageContent?The picture on the left.
afterDiffineImageContent?The picture on the right.
beforeLabelString?What the header calls the left side.
afterLabelString?What it calls the right side.
picturesList<DiffineImageContent>?Several pictures rather than two. Turns the list on.
pictureLabelsList<String>?What each of them is called.
baselineint0Which of them the rest are counted against.
picturesResultDiffImagesResult?A comparison of the list already worked out.
onPicturesDiffValueChanged<DiffImagesResult?>?The comparison of the list.
onChooseFuture<DiffineImageContent?> Function(DiffineSide)?A reader asked for a picture. Editor only.
onDiffValueChanged<DiffImageResult?>?The comparison, every time it is worked out again.
resultDiffImageResult?A comparison already worked out. The pictures are still drawn.
diffDiffImageOptionskDiffineImageDefaultsHow the two are compared. See diffImage.
maxPixelsint4000000How many pixels a picture is decoded at, at most.

DiffineImageContent is a sealed class with three shapes: DiffineEncodedImage around the bytes of a file, DiffineDecodedImage around a ui.Image the application already has, and DiffinePixelImage around a DiffPixels. A URL is not among them — fetching one is the application's to do, and what arrives here is what it already holds.

onChoose is the editor's, and it is the whole of it: the widget asks for a picture for one side and the application answers with one, or with null for a reader who changed their mind. A file picker is a plugin and a permission, and neither belongs inside a diff viewer.

The view

PropTypeDefaultWhat it decides
view'split' | 'overlay' | 'wipe' | 'mask''split'How the two are laid out.
unchanged'keep' | 'dim' | 'hide''keep'What is done with the parts nothing happened to.
wheel'zoom' | 'pan''zoom'What the wheel does over a pane.
loupebooleantrueWhether the pixels under the pointer are shown magnified.
fadenumber0.5How much of the second picture is let through. Overlay only.
onFadeChange(fade: number) => voidThe overlay was faded.
wipenumber0.5Where the line between the two is, from 0 to 1. Wipe only.
onWipeChange(wipe: number) => voidThe line was moved.
marksbooleantrueWhether the pixels that changed are tinted.
outlinesbooleantrueWhether a box is drawn round each change.
headerbooleantrueWhether each side is named above it.
navigationbooleantrueWhether the buttons for stepping through the changes are drawn.
zoombooleantrueWhether the zoom controls are drawn.
summarybooleantrueWhether the bar under the panes is drawn.
colorScheme'system' | 'light' | 'dark''system'Which palette to draw in.
locale'en' | 'ko''en'The language of the component's own words.
stringsPartial<DiffineImageStrings>Words to use instead of the locale's.

split draws two panes; the other three draw one, with both names over it. What the marks are drawn in is five custom properties rather than props, because a canvas is painted rather than styled.

ArgumentTypeDefaultWhat it decides
viewDiffineImageViewDiffineImageView.splitHow the two are laid out.
unchangedDiffineImageUnchanged.keepWhat is done with the parts nothing happened to.
wheelDiffineImageWheel.zoomWhat the wheel does over a pane.
loupebooltrueWhether the pixels under the pointer are shown magnified.
fadedouble?0.5How much of the second picture is let through. Overlay only.
onFadeChangedValueChanged<double>?The overlay was faded.
wipedouble?0.5Where the line between the two is, from 0 to 1. Wipe only.
onWipeChangedValueChanged<double>?The line was moved.
marksbooltrueWhether the pixels that changed are tinted.
outlinesbooltrueWhether a box is drawn round each change.
headerbooltrueWhether each side is named above it.
navigationbooltrueWhether the buttons for stepping through the changes are drawn.
zoombooltrueWhether the zoom controls are drawn.
summarybooltrueWhether the bar under the panes is drawn.
colorSchemeDiffineColorScheme.systemWhich palette to draw in.
themeDiffineTheme?The whole palette, and the measurements with it.
heightdouble?How tall the whole comparison is.
localeDiffineLocaleDiffineLocale.enThe language of the widget's own words.
stringsDiffineStrings?Words to use instead of the locale's.

split draws two panes; the other three draw one, with both names over it. What the marks are drawn in is theme.image, seven colours of the palette.

Moving around

PropTypeDefaultWhat it decides
viewportDiffineImageViewport | 'fit'Where a reader is looking.
defaultViewportDiffineImageViewport | 'fit''fit'Where to start looking.
onViewportChange(viewport: DiffineImageViewport) => voidA reader moved or zoomed, or a button did.
selectednumberWhich change a reader has stepped to, or -1.
defaultSelectednumber-1Which change to start on.
onSelectedChange(selected: number, region: DiffImageRegion | null) => voidA change was stepped to.

DiffineImageViewport is { scale, x, y }: how many screen pixels one pixel of the frame is drawn as, and the point of the frame the middle of the pane is looking at. Both panes are given the same one, which is what makes a split view move together.

ArgumentTypeDefaultWhat it decides
viewportDiffineImageViewport?Where a reader is looking. null fits the frame to the pane.
onViewportChangedValueChanged<DiffineImageViewport>?A reader moved or zoomed, or a button did.
selectedint?Which change a reader has stepped to, or -1.
defaultSelectedint-1Which change to start on.
onSelectedChangedvoid Function(int, DiffImageRegion?)?A change was stepped to.

DiffineImageViewport is { scale, x, y }: how many logical pixels one pixel of the frame is drawn as, and the point of the frame the middle of the pane is looking at. Both panes are given the same one, which is what makes a split view move together. There is no defaultViewport: null is the fit, and it is also where an uncontrolled pane starts.

Released under the MIT License