A cross-platform command-line tool (Windows, Linux, macOS) for combining two portrait photos into a single landscape image. This is useful for digital photo frames that display vertical images awkwardly.
Many digital photo frames are designed with landscape orientation in mind.
When they encounter portrait images, they often resort to awkward workarounds - adding black bars to the sides (pillarboxing) or cropping the top and bottom to fill the frame. Both approaches compromise the photo: either by shrinking it down to an underwhelming size or by cutting out important parts of the image. For users who care about presentation, this default behaviour is ugly.
SideBySide works around this problem by combining two portrait images into a single landscape composition. This ensures each photo is displayed without cropping or black bars. The combined image is also resized to match your frameβs resolution, reducing storage use without affecting visible quality.
Tip
- Got a messy photo collection? GroupMachine can automatically sort photos and videos into albums.
- Manage your photos with Picasa? StarCasa outputs a list of your favorite portraits ready for SideBySide.
- π» Runs on Windows, Linux (x64 & ARM) and macOS (Intel & Apple Silicon).
- π Accepts multiple source folders in a single run.
- π Supports file lists for precise control over included images.
- π Recursively scans subfolders when enabled.
- πΌοΈ Combines two portrait
.jpgimages into one seamless landscape.jpg. - π§ Detects and avoids pairing visually identical photos.
- β Adds an optional divider to enhance visual separation between images.
- π― Automatically centers each photo within a black background.
- π Resizes output to optimally fit digital photo frame displays.
- π Creates uniquely named files to prevent duplicates and maintain consistency.
- π Supports sorting photos by date taken/created or random order.
- πͺ Mirrors output folder by removing stale files no longer in source.
- π°οΈ Preserves original timestamps (when sorted).
Get the latest version from https://github.com/mrsilver76/sidebyside/releases.
Each release includes the following files (x.x.x denotes the version number):
| Filename | Description |
|---|---|
SideBySide-x.x.x-win-x64.zip |
β For Microsoft Windows 10 and 11 β¬ οΈ Most users should choose this |
SideBySide-x.x.x-linux-x64.zip |
For Linux on Intel/AMD CPUs |
SideBySide-x.x.x-linux-arm64.zip |
For Linux on ARM (e.g. Raspberry Pi) |
SideBySide-x.x.x-osx-arm64.zip |
For macOS on Apple Silicon (eg. M1 and newer) |
SideBySide-x.x.x-osx-x64.zip |
For macOS on Intel-based Macs (pre-Apple Silicon) |
| Source code (zip) | ZIP archive of the source code |
| Source code (tar.gz) | TAR.GZ archive of the source code |
- Download the
.zipfile for Windows (see the table above) and extract it by right-clicking and selecting "Extract All.." - Ensure that the extracted
libSkiaSharp.dllremains in the same directory as the binary - this is required for the native SkiaSharp bindings to work. - Open a Command Prompt in that folder and run the program with your desired arguments.
- Download the appropriate binary for your platform (see table above).
- Ensure that the extracted
libSkiaSharp.dylibfile remains in the same folder as the binary - this is required for the native SkiaSharp bindings to work. - Install the .NET 8.0 runtime. Slightly more technical information can be found here.
β οΈ Do not install the SDK, ASP.NET Core Runtime, or Desktop Runtime.- Make the downloaded file executable:
chmod +x SideBySide-x.x.x-<your-platform> - Open a terminal in that folder and run the program with your desired arguments.
- If you get
zsh: killedwhen running the executable then:- Apply an ad-hoc code signature:
codesign --force --deep --sign - SideBySide-x.x.x-<your-platform>codesign --force --deep --sign - libSkiaSharp.dylib
- Remove the quarantine attribute:
xattr -d com.apple.quarantine SideBySide-x.x.x-<your-platform>xattr -d com.apple.quarantine libSkiaSharp.dylib
- Apply an ad-hoc code signature:
- Download the appropriate binary for your platform (see table above).
- Ensure that the extracted
libSkiaSharp.sofile remains in the same folder as the binary - this is required for the native SkiaSharp bindings to work. - Install the .NET 8.0 runtime. Slightly more technical pages can be found here.
β οΈ Do not install the SDK, ASP.NET Core Runtime, or Desktop Runtime.- Make the downloaded file executable:
chmod +x SideBySide-x.x.x-<your-platform> - Open a terminal in that folder and run the program with your desired arguments.
- Tested extensively: Windows 11
- Tested moderately: Linux (ARM)
- Not tested at all: Windows 10, Linux (x64), macOS (x64 & Apple Silicon)
Here are some examples for using SideBySide. They will work on all platforms.
SideBySide "C:\Users\Richard\Pictures\Holiday" -o "C:\Users\Richard\Pictures\Frame" -d 1080x720 -c
SideBySide "~/Pictures/Holiday" --output "~/Pictures/Frame" --dimensions 1080x720 --clean
- Look for portrait images in the
Pictures\Holidayhome folder - Output landscapes images in the
Pictures\Framehome folder - Delete previously generated images in
Pictures\Framebefore starting - All landscape images to be 1080 x 720
- Use no pixel separator (where possible)
SideBySide "C:\Users\Richard\Pictures\Holiday\Europe" "C:\Users\Richard\Pictures\Holiday\America" -o "C:\Users\Richard\Pictures\Frame" -d 1080x720 -g 10 -r -w
SideBySide "~/Pictures/Holiday/Europe" "~/Pictures/Holiday/America" --ouput "~/Pictures/Frame" --dimensions 1080x720 -gap 10 --recursive --write
- Look for portrait images in the
Pictures\Holiday\EuropeandPictures\Holiday\Americahome folders (and all sub-folders) - Output landscapes images in the
Pictures\Framehome folder - All landscape images to be 1080 x 720
- Use a pixel separator of no less than
10pixels - Overwrites any previously generated images in
Pictures\Frame
SideBySide is a command-line tool. Run it from a terminal or command prompt, supplying all options and arguments directly on the command line. Logs with detailed information are also written and you can find the log file location using -h (--help).
Usage: SideBySide [<input_dir>...] -o <output_dir> -d <WxH> [options]
<input_dir>...
One or more directories containing portrait.jpgor.jpegimages. Landscape or square images will be ignored.
Note
At least one input directory or the -f (--filelist) option is required.
-
-o <output_dir>,--output <output_dir>
Directory where generated landscape images will be saved. Must already exist. -
-d <WxH>,--dimensions <WxH>
Output image dimensions specified as[width]x[height]or[width],[height](e.g.1080x720or1080,720).
Tip
For optimal display quality, set the output dimensions to match the native resolution of your digital photo frame. This ensures images are scaled accurately without distortion or unnecessary padding.
-
-r,--recursive
Recursively scan all subdirectories beneath each source folder. Only files with supported extensions will be considered. -
-f <file>,--filelist <file>
Use a plain text file (<file>) to explicitly define which images to process. Each line must contain the full path to a .jpg or .jpeg image. Empty lines and invalid paths will be silently ignored, meaning that messy or autogenerated lists can be used.This option is ideal when:
- You want full control over exactly which images are processed, regardless of folder structure.
- You're using a third-party tool like StarCasa to generate a
txtfile of starred photos in Google's Picasa or similar apps.
Note
Using -f (--filelist) does not require you to provide a folder for processing. However if you specify both a file list and input folders, all valid images from both sources will be used.
-g <pixels>,--gap <pixels>
Minimum width in pixels of the black separator between images. If not provided or set to0then extra separation will be avoided where possible.
Important
Black borders on the left and right may appear depending on the aspect ratio of the original images. The separator width defines the minimum number of pixels placed between the two images. If natural spacing already exceeds this value due to image proportions, no additional separator will be added.
-
-w,--write
Overwrite existing output images if they already exist in the destination directory. -
-c,--clean
Clean/remove all generated images from the destination folder before processing begins. Only.jpgfiles with names starting withsideby-will be deleted. -
-m,--mirror
Enable mirror mode for the output directory. Any previously generated files (matchingsideby-*.jpg) in the destination folder that are not recreated or deliberately skipped in the current run will be deleted. This ensures the output folder is an accurate mirror of the current inputs - no outdated or stale composite images will be left behind.This is particularly useful when you regularly update or rotate source content, you use file lists (
-for--filelist) for precise input control and/or you want a clean output folder without accumulating unused files.
Note
Mirror mode only affects files generated by this tool (sideby-*.jpg). It will not touch other files in the output directory.
-
-s,--shuffle
Shuffle/randomise the order of input images instead of sorting by date taken/created. When enabled, the output images will have their creation and modification timestamps set to the time of processing rather than preserving the originals. -
-nc,--no-check
Disables GitHub version checks for GroupMachine.
Note
Version checks occur at most once every 7 days. SideBySide connects only to this URL to retrieve version information. No data about you or your photo library is shared with the author or GitHub - you can verify this yourself by reviewing GitHubVersionChecker.cs
-
-v,--verbose
Enables detailed logging output for debugging and progress tracking. This information is always included in the logs. -
/?,-h,--help
Displays the full help text with all available options, credits and the location of the log files.
Each generated file is uniquely named using a short SHA256 hash derived from the full paths of the two source images.
For example:
sideby-Z9dcF2gqex7o7RGn.jpg
sideby-gBX41a-GkfcaaBA4.jpg
sideby-QgVzwyOYxBrFTEjb.jpg
The filename is deterministic: given the same pair of input files, the output name will always be the same - regardless of options like separator width or output resolution. This ensures consistency across runs and avoids unnecessary duplication.
Note
By default, the tool will not overwrite existing files. If you change visual settings and want updated images, use the --write flag to force regeneration.
When images are sorted by date (default), the output fileβs creation and modification times are set to the earliest of the two input images. When using --shuffle, timestamps are not modified to avoid implying an artificial order.
Please raise an issue at https://github.com/mrsilver76/sidebyside/issues.
SideBySide currently meets the needs it was designed for, and no major new features are planned at this time. However, the project remains open to community suggestions and improvements. If you have ideas or see ways to enhance the tool, please feel free to submit a feature request.
- Frame icon created by Freepik - Flaticon (https://www.flaticon.com/free-icons/frame)
- Image manipulation powered by SkiaSharp, a .NET wrapper around Google's Skia 2D graphics library.
- Photo: Coconut Tree Near Body of Water Under Blue Sky by Asad Photo Maldives.
- Photo: Person Laying On Sand by Rebeca GonΓ§alves.
- Photo: Brown Wooden Panel by Tirachard Kumtanom.
- Added image comparison to prevent identical photos from being paired together.
- Fixed issue where photos rotated via EXIF data were incorrectly detected as landscape.
- Improved usage output: commands are now grouped by category and wrap correctly across terminal widths.
- Logger now includes OS information to help with troubleshooting across platforms.
- Added GitHub version checking with new
-nc(--no-check) option to disable it. - New header displays all key configuration information.
- Improved logger performance by keeping files open instead of repeatedly opening/closing.
- Split utility functions into static classes for clearer structure.
- Resolved all .NET code analysis warnings to standardize style and tidy the codebase.
- Updated publish Powershell script.
- π Declared as the first stable release.
- Added
--mirroroption. - Added
--filelistoption. - A destination folder will now be created if one doesn't previously exist.
- Cleaned up
--helpoutput. - Logger now includes OS information to help with troubleshooting across platforms.
- Cleaned up various pieces of code (analyzer suggestions regarding naming, simplifications, and style)
- Initial version.
