At Transloadit, it is our desire to give you the best possible encoding experience, and one of the main factors involved is making sure that we are using the latest encoding tools.
It's no secret that we are heavily relying on open source technology for this, and we therefore make a conscious effort to give back to the giants on whose shoulders we are standing, for instance through providing free hardware to the ImageMagick project.
Even though we have an intimate relationship with the project, it has proven to be very problematic for us to modernize our stack.
The 'fun' parts of maintaining encoding software
Oftentimes, our customers rely on behavior of the old version (even on its bugs, but more on that
later), so "just upgrading" - which we naively tried to do a few years back - breaks their workflow.
To ensure we can provide a consistent experience even between
operating system upgrades, we are
required to compile ImageMagick and all of its 31 dependencies. Yes 31, because by adding libraries
ufraw, we gain support for 131 more
formats than what a standard
apt-get install imagemagick would provide on Ubuntu. And by including libraries all the way down
zlib, we also shield ourselves from any backwards compatibility breakage that could occur as a
result of operating system upgrades, thereby allowing our encoding capabilities to be the reliable
constant upon which our customers can build their businesses.
Besides vast support for formats and this guaranteed continuity, the advantage of compiling is that it allows you to create a static binary. This is a single executable file that has every dependency baked into it. This way, it becomes easy to run very different versions of the same software alongside one another without any conflicts, giving our customers an upgrade path that is opt-in. This is how we have been providing upgrades to our video encoding software.
The problem, however, that we are having now with upgrading our image stack, is that the process of compiling ImageMagick and its 31 dependencies would take two hours, and that is under perfect conditions on a beefy server. If one particular library version conflicted with another, you may only find out after 90 minutes, after which you would have to think of a possible fix and then try all over. That can hardly be considered workable or maintainable, and it certainly does not amount to the level of agility that we aspire to have in our development.
The introduction of Docker containers, we thought, would solve some of
this struggle in that we could at least run different versions alongside each other easier, and
speed up our build times through the use of layers. Also, instead of having single massive binaries
to recompile, we could just recompile a single shared library if needed. Our initial joy was
tempered when we learned that there was no way to start containers without dangerous system
privileges. This meant we either had to keep containers running and let our non-privileged API user
proxy commands to those, or be okay with giving this user the privilege to start containers. Both
options weren't great, to say the least. Proxying would be quite fragile, and by handing out such
privileges, an attacker could theoretically mount any directory on the host machine as a
into the container, and then read/write/delete whatever they wanted. We later also learned about the
work that was being done in the runC project to launch
containers without root access (and we'll likely
take that approach in the future) but it wasn't yet at the level of maturity that we needed.
We also considered running a separate cluster for the new version, but this would have some serious costs in terms of additional hardware, maintenance and operational overhead.
We dove deep into all these different directions, but ended up having to back out every time since there were serious drawbacks. How should the future of our stack maintenance look? What solution is both reliable and mature enough to give us guaranteed continuity while maintaining our agility?
For a long time, we had also been looking at Nix as a possible solution. We have been building our software with a simple compile framework called depmake that Felix wrote back in 2012 and which already implemented some of Nix' basic ideas and qualities. It has been working very well for us all these years, but Nix takes these, and additional concepts, much further.
Nix is a functional programming language that lets you define how to build software in a declarative fashion. By making use of a global cache, you only have to compile things that are unique to your environment, but in many cases, you can just pull the resulting software straight from this cache. This can greatly improve build times for the simple fact that there is no building *waves hand* 👋
After a good amount of learning and experimentation, we figured out how Nix and depmake could co-exist, so that we can start making use of the new, without throwing away the old.
Using Nix really opens the floodgates to potential new software and is going to make it extremely easy for us to roll out a new stack, customize it, ensure it keeps working in the same way forever, and allow multiple versions of the same software to exist alongside one another.
The bulk of the work in providing a new stack to our customers has now moved from building to testing it, which is a great improvement.
We are still considering wrapping our Nix-built software in containers and running them root-less, but this would primarily be to deliver additional security on top of the containment that we already provide.
Will we ever deprecate our old versions? Yes. But we'll do so with fairly lengthy grace-periods during which we gently nudge people towards the new.
Without further ado.. version two!
We are quite proud to present to you our new ImageMagick stack nicked
v2.0.3 (not SemVer, any
change can potentially break backwards compatibility). It has been in private beta for two weeks
already and will now enter public beta. It is based on the latest ImageMagick version that Nix
provides but with custom additions such as
libraw so that our customers can keep enjoying those
131 extra formats.
Besides RAW and all the other formats that we already support, customers will be happy to learn that the new stack comes with support for both WebP and DjVu formats.
We have updated our supported formats page so you can more easily discover differences between our stack versions. Simply click the "Compare" button for a clear overview.
For your convenience, here are the full lists:
Gained read support: 3FR, 3G2, 3GP, CANVAS, CLIP (previously write-only), FILE, FTP, HALD, HTTP, IIQ, JNX, MAC, MEF, NRW, PES, RAW, RMF, RW2, SCREENSHOT, WMF, WMZ.
Gained write support: BRF, DDS (previously read-only), INLINE (previously read-only), ISOBRL, ISOBRL6, JSON, SPARSE, UBRL, UBRL6
Gained read/write support: AAI, BGRA, BGRO, CAL, CALS, DXT1, DXT5, G4, GROUP4, HDR, JPE, JPS, MASK, MKV, PNG00, PNG48, PNG64, PSB, RGF, SIX, SIXEL, TIFF64, VIPS, WEBP
Besides more formats, our new ImageMagick version also enables HDRI (High Dynamic Range Imaging) by default, so you can expect more accurate image processing results.
Aside from these customer-facing upgrades, we're quite happy about the fact that the new version also has some operational benefits for us, such as the plugging of a number of security vulnerabilities, memory leaks, and so forth.
Notable breaking changes
Our previous version of ImageMagick had a bug in colorspace handling, resulting in darker images and the incorrectly reporting of sRGB vs RGB. This is now fixed, but if you found a way to work around this bug, you are essentially relying on it and will therefore need to un-compensate when you choose to upgrade.
We no longer support reading PGX and JPX files, and for the formats: BRG, GBR, GRB and RBG, it is now recommended to create an RGB format and then swap channels afterwards.
For reference, you can find a full changelog at the bottom of this post.
Transloadit recommends to start using the new ImageMagick stack for testing and non-critical
workloads. You can do this by adding
imagemagick_stack: "v2.0.3" in your
/image/resize Robot Step.
Our apologies for taking so long to deliver on this upgrade, you can expect much timelier updates thanks to our new way of stack herding! 😄
We'd love to hear your findings, so do let us know what you think via the well known speech bubble on our site!
For reference, here is the full changelog:
- Added additional checks to DCM reader to prevent data-driven faults (bug report from Hanno Böck)
- Added final
ClampToQuantumin sigmoidal colormap loop
- Added support for languages that require complex text layout (reference)
- Added tanh/atanh clone of legacy sigmoidal map (faster & more accurate)
- Added define
psd:additional-infoto preserve the additional information in a PSD file
- Added define
psd:preserve-opacity-maskto preserve the opacity mask in a PSD file
- Added layer RLE compression to the PSD encoder
- Added layer RLE compression to the PSD encoder
- Added support for GROUP4 compression to the FAX coder
- Added support for RGB555, RGB565, ARGB4444 and ARGB1555 to the BMP encoder (reference)
- Allowed the use of set and escapes when no images in memory (unless you attempt to access
per-image metadata). Currently does not include
- Applied Debian patches (reference)
- Backoff finite precision epsilon (reference)
- Bumped Magick++ SO. Previously, a global replace changed
- Can read geo-related EXIF metdata once-again (reference)
- Check for buffer overflow in
- Coder path traversal is not authorized (bug report provided by Masaaki Chida)
- coders/png.c: Added support for a proposed new PNG chunk (exIf read-write, eXIf read-only) that is currently being discussed on the png-mng-misc at lists.sourceforge.net mailing list
- coders/png.c: Added support for a proposed new PNG chunk (zxIf, read-only) that is currently being
discussed on the png-mng-misc at lists.sourceforge.net mailing list. Enable exIf and zxIf with
CPPFLAGS="-DexIf_SUPPORTED -DxzIf_SUPPORTED"If exIf is enabled, only the uncompressed exIF chunk will be written and the hex-encoded zTXt chunk containing the raw Exif profile won't be written.
- Correct for numerical instability (reference)
- Correction to composite
- Deny indirect reads by policy, remove policy to permit, e.g.,
- Distort no longer converts
- Do not close path for linejoins of round (reference)
- Document behavior change in the security policy (thanks to yoya)
- Don't interpret
-fxoption arguments (reference)
- Don't return a zero bounding box for
- Don't set background for transparent tiled images (reference)
- Don't set update trait on alpha channel (private e-mail concerning
- Don't sync pixel cache in
AcquireAuthenticCacheView()(bug report from Hanno Böck)
- Eliminate compiler warning
- Enable alpha channel if background color is non-opaque (reference)
- Evaluate lazy pixel cache morphology to prevent buffer overflow (bug report from Ibrahim M. El-Sayed)
- Fix compile error in
- Fix drawing glitch for stroke widths greater than 2 (reference)
- Fix for possible security vulnerabilities (reference)
GetNextToken()off by one error
- Fix memory leak in the MPC format
- Fix MVG stroke-opacity (reference)
- Fix pixel cache on disk regression (reference)
- Fix possible buffer overflow when writing compressed TIFFS (vulnerability report from Cisco Talos, CVE-2016-8707)
- Fix re-declaration of i (at the top, and inside a conditional)
- Fix small memory leak (patch provided by Андрей Черный)
- Fix stroke offset problem for
- Fixed fd leak for WebP coder (reference)
- Fixed improper scaling of certain FITS images (reference)
- Fixed incorrect padding calculation in PSD encoder
- Fixed incorrect parsing with ordered dither (reference)
- Fixed incorrect RLE decoding when reading a DCM image that contains multiple segments
- Fixed incorrect RLE decoding when reading an SGI image (reference)
- Fixed issue where the display window was used instead of the data window when reading EXR files (reference)
- Fixed memory leak when creating nested exceptions in Magick++ (reference)
- Fixed proper placement of text annotation for
- Fixed reading DXT1 images with an alpha channel.
- If a convenient line break is not found, force it for caption: (reference)
- Implemented a private PNG caNv (canvas) chunk for remembering the original dimensions and offsets when an image is cropped. Previously we used the oFFs and vpAg chunks for this purpose, but this had potential conflicts with other applications that also use the oFFs chunk
- Increase memory allocation for TIFF pixels (reference)
- Initialize draw_info alpha member to OpaqueAlpha
- Initialize index channel to get expected results from the stegano coder
- Iterate channels over source image rather than destination (bug report from Hanno Böck)
- Mask composite produces proper results for the
- Monochrome images no longer have inverted colors (reference)
- Note alpha channel when combining 4 or more images (reference)
- Off by 1 error when computing the standard deviation (reference)
- Off by one memory allocation (reference)
- Patch so
-kuwaharaoption can preserve colormapped edges
- Permit EPT images with just a TIFF or EPS image, not both (reference)
- Prevent buffer overflow (bug report from Max Thrane)
- Prevent buffer overflow and other problems in SIXEL, PDB, MAP, TIFF and CALS coders (bug report from Donghai Zhu)
- Prevent buffer overflow in BMP & SGI coders (bug report from pwchen&rayzhong of tencent)
- Prevent buffer overflow when streaming an image (reference)
- Prevent fault in MSL interpreter
- Prevent memory use after free (reference)
- Prevent possible buffer overflow when reading TIFF images (bug report from Shi Pu of MS509 Team)
- Prevent possible shell command injection vulnerability through the authenticate parameter of the PDF, PCL and XPS coders (report from Erez Turjeman)
- Prevent random pixel data for corrupt JPEG image (bug report from Hirokazu Moriguchi, Sony)
- Prevent spurious removal of MPC cache files (reference)
- Process channels independently for
- Properly auto-fit caption (reference)
- Properly center text label (reference)
- Properly initialize PES blocks (reference)
- Quote passwords when passed to a delegate program.
- Rather than replicate
artifactsmake a link from image to
image_infoand lookup a global option if no artifact is defined.
- Recognize XML policy closing tags (reference)
- Remove https delegate
- Remove OpenMP calls from colormap update loops
- Remove support for internal ephemeral coder
- Render to clip mask rather than image for clip-path MVG graphics primitive
- Replace show delegate title with image filename rather than label
- Respect gravity option (reference)
- Return correct offset for negative index for
- Return unbiased standard deviation for image statistics (reference)
- Revert patch that did not set update trait on alpha channel
- RLE check for pixel offset less than 0 (heap overflow report from Craig Young)
- Sanitize all delegate embedded formatting characters
- Sanitize comments that include braces for the MIFF image format (reference)
- Sanitize input filename for http / https delegates (improved patch)
- Security improvements to TEXT coder broke it (reference)
- Set alpha member of draw structure to
- Set colorspace to
-appendhas non-homogenous colorspaces (reference)
- sigmoidal-contrast: Direct computation, without LUT
- sigmoidal-contrast: Remove unnecessary initial
- Support configure script
--enable-indirect-readsoption to enable indirect reads (
@) in filenames
- Support configure script
--enable-pipes optionto enable pipes (
|) in filenames
- Support pixel-cache and shred security policies
- Support read-masks for the
- Support the compare
- Support the
-cloneoption no longer leaks memory
-extentoption now matches the results of IMv6 (reference)
-streamoption now increments the pixel pointer properly (reference)
- The histogram coder now returns the correct extent
- To comply with the SVG standard, use
stroke-opacityfor transparent strokes
- Turn off alpha channel for the compare difference image (reference)
- Unbreak build without JPEG support (reference)
- Uninitialized data in MAT image format (reference)
- Unit test pass again after small SUN image patch
- Validation unit test for MNG works again