The technique is mentioned in one of Wolfram CDF virtual conference talks (See the course:
Developing Real-World CDF Applications), as well as being used in a lot of CDF examples (for instance, the slideshow at the beginning of this example) but I will repeat it here, with some improvement.
Recompressing images with better compression
(Note: While writing an answer, Rojo already provided the same method.)
As it is mentioned by other users here, you don't have to manually use Compress
since images are already being compressed using it when being stored inside a notebook cell. Compress
is based on gzip and it is overall OK, except that it can be pretty horrible with images.
Let's try to use other compression, for instance PNG (lossless) or JPEG (lossy) (or anything for that matter). Let's say our image is img
. I am grabbing an image from the Hubble Gallery as an example.
url = "http://imgsrc.hubblesite.org/hu/db/images/hs-2006-01-a-1920x1200_wallpaper.jpg";
img = Import[url];
This is a pretty big image.
In[3]:= ByteCount[img]
Out[3]= 6912464
Now, compress it using the compression of your choice and store it as string using ExportString
.
jpeg = ExportString[img, "JPEG"];
Of course, you can use any other compression (for instance, "PNG" or "GIF") or control compression rate for JPEG by using "CompressionLevel"
option (default is 0.25).
To embedded this data in a cell, it has to be converted to Base64
(essentially using non-special characters).
base64 = ExportString[jpeg, "Base64"]
This is a slightly larger than actual binary JPEG, but still far smaller than uncompressed size, or compressed size.
In[6]:= ByteCount[base64]
Out[6]= 219224
(To be fair, Mathematica will save it using aforementioned Compress
and the size will be smaller than what ByteCount[img]
is reporting, but not this small).
By using ImportString
, you can convert it back to the image:
ImportString[base64, "Base64"]
(You don't have to call ImportString
again for the compression, since it will be automatically taken care of)
To embedded the raw data (jpeg
) and let FrontEnd uncompress it during the time of reading a notebook, try the following code.
With[{a = base64}, Dynamic[ImportString[a, "Base64"], SingleEvaluation->True]]
or
With[{a = base64}, Dynamic[Refresh[ImportString[a, "Base64"], None]]]
The extra option (SingleEvaluation
) and Refresh
are used to make sure that it is evaluated just once. Also, With
is needed to ensure that the embedded cell contains the content of base64
, instead of a symbol base64
. This can play nicely with other cells, such as texts and other graphics, using Row
or other constructions.
Now, let's try to save it. First try:
nb = CreateDocument[With[{a = base64},
Dynamic[ImportString[a, "Base64"], SingleEvaluation -> True]]];
NotebookSave[nb, "jpeg.nb"]
Try to check the size of the file, and you will be surprised...

It is smaller than a notebook with just the image (which will be around 5MB), but still not quite as smaller as we expected it to be. What's wrong? It is because Mathematica caches images by default. Let's disable it (CacheGraphics->False
will do it. You can set it using the Option Inspector too).
nb2 = CreateDocument[With[{a = base64},
Dynamic[ImportString[a, "Base64"], SingleEvaluation -> True]], CacheGraphics->False];
NotebookSave[nb2, "jpeg2.nb"]
Now, much reasonable:

Using native compression of imported image
Sometimes, your image is coming from external source with its own native compression. The problem with the first approach is that it essentially uncompress it then recompress using different compression / rate (sort of transcoding...). In particularly with lossy compression like JPEG, it may leads to image distortion. To minimize this, you can do the following.
(Note: Szabolcs provided a nice solution using Import
. Thank you)
We can read the file's native binary data by caling Import[..., "String"]
.
jpeg = Import[url, "String"];
First, the image has to be in local storage. Bring its native binary data using BinaryReadList
. Also, use "Character8"
and StringJoin
so that it will turn into the string of binary data.
jpeg = StringJoin@@BinaryReadList["hs-2006-01-a-1920x1200_wallpaper.jpg", "Character8"];
It should be about the same size as the original file (+/- some due to the string representation in Mathematica). From here, you can follow the above steps to turn it into "Base64" and then embed.
Reading image from online
If you have a lot of images, then this can be applicable.
Dynamic[Refresh[Import[url], None]]
If it times out, you can increase the timeout using DynamicEvaluationTimeout
or try the following to show a nice indicator while loading.
DynamicModule[{img = None},
Dynamic[If[img === None,
ProgressIndicator[Clock[Infinity], Indeterminate], img],
TrackedSymbols :> {}],
Initialization :> (img =
Import["http://imgsrc.hubblesite.org/hu/db/images/hs-2006-01-a-1920x1200_wallpaper.jpg"]),
SynchronousInitialization -> False]
This course notebook contains some useful examples including a spinner for progress and such.
Also, these techniques can be used to embed any large data, such as MOV file!