create new tag
view all tags


The GhostProof project involves using Ghostscript and MuPDF together to provide an interactive color correct proofing tool. Ghostscript will process the input PDF so we get color correct output, and MuPDF will handle the interactive display of that output.

The workflow (for the android app at least) is as follows:

  • The viewer app is invoked on the input file (postscript, PDF, XPS etc).
  • This file is converted to PDF if it is not already (using Ghostscript and pdfwrite).
  • The converted file is then loaded into the viewer as normal.
  • When the viewer enters 'proofing' mode, it creates a .gproof file from the input pdf file. The viewer then invokes itself again on this .gproof file.
  • The gproof file contains enough information for the viewer to display the correct number of correctly sized pages.
  • As the viewer requests a page to be rendered, MuPDF invokes gs in the background to produce the appropriate .gprf file. This is then read and the page returned.

The viewer UI displays a list of separations for each page, and enables them to be turned on and off. If all separations are turned on, then MuPDF will use the color correct rgb representation from the .gprf file. If not, it will form its own (non-color correct) composite from the separations and equivalent cmyk colors given in the .gprf file.

Building a GhostProof viewer

For Android

First, you need to a libgs.so. Either you can use a prebuilt one or you can build your own:

Prebuilt libgs.so


Building yourself.

Check out: android_mupdf_gs_so branch on robin's ghostscript repo

Change directory into gs.

Copy Makefile.android as Makefile (important, as the so targets recursively invoke Makefile).

Edit the paths in Makefile as appropriate for your ndk installation.

And then: make so (or make sodebug)

This should produce a sobin/libgs.so (or sodebugbin/libgs.so).

Then run 'arm-linux-androideabi-strip sobin/libgs.so'. If we don't strip the lib fails to start up with a -100 error. No idea why. In any event stripping is probably a good idea.

Building MuPDF

Checkout MuPDF from robin's repo (master branch).

Copy libgs.so from the previous stage to platform/android/jni/libgs.so

Do the usual android build, but add


to the end of the ndk-build line.

That's it!

Using Android Studio

The Android project contains a Gradle build file that can be used with Android Studio. Note that this is simply a wrapper around the existing NDK makefiles, and they are still required.

First, get the source code and update the subprojects as you normally would.

Then, build the muPDF library with gproof support, like this:

The first time, launch Android Studio and import the platform/android folder. You'll be asked if you want to set up the gradle wrappers, choose OK to do this. Once this is done, Android Studio will remember this and all you need to do is open it next time.

You may see an error saying that your gradle version is too old. There is a link in the error window you can click to fix this automatically. It's just editing gradle-wrapper.properties with a link to the correct version.

Now, get or build a version of libgs.so that also has gproof support. See http://twiki.ghostscript.com/do/view/MuPDF/GhostProof for details. Copy this file into the JNI folder.

Edit local.properties and specify which version of the NDK to use. Newer versions will work, but version r8e will result in an app that works with our minimum platform.

Now build and run using Android Studio. You should be able to debug the Java code.

You can also do command-line builds using the gradle wrappers (gradlew and gradlew.bat), which are simply scripts that are used by Android Studio when it builds.

Using the App

Install MuPDF as built above. Run the app. This will give you the normal file picker. Pick a PDF file. This will give you the standard MuPDF view on the file.

On the top bar, click (+) to go to the 'more' menu. There should be a 'P' icon to the left of the print icon. 'P' is intended to mean 'Proof' - any suggestions for better icons gratefully received. Click 'P' and the app goes into proofing mode.

Actually what happens is that the app writes out a '.gproof' file based upon the current PDF file, and then invokes itself to view that gproof file. When the proofing viewer shuts down, the original viewer deletes the gproof file and continues.

Things to do with the viewer

The viewer is just at proof of concept stage. Lots of work to do on it.

  • The 'P' icon could be replaced by something better.

  • The UI should change when in 'proofing' mode; annotation/searching options etc should be hidden, probably printing too. The 'P' icon should be hidden in proofind mode.

  • The UI should offer a separations view; something like a list of 'swatches' across the top bar that can be enabled/disabled. Too many separations to fit on the screen means the bar should scroll left/right. Somehow we should show separation names; maybe show the names running downwards at 45 degrees from each swatch? Fade the names out as the top bar disappears off the screen. Potentially Different separations on each page.

  • Need to expose a JNI interface to read number of separations, separation names and enable/disable separations etc.

  • Probably need a way to set icc profile options within the MuPDF API?

  • Probably need a way to specify the proofing resolution - when we invoke the proofing mode, we should prompt for the desired resolution. Currently it's hardwired to 300 dpi. If we change the dpi in the .gproof generation, it should follow through all the rest of the stages nicely.

  • The GS device needs work. Performance improvements would be good - potentially we could improve a lot - do some profiles. Equivalent colors don't appear to being produced (for CMYK at least). RGB is generated dumbly, so spots aren't showing up at all at present. ICC profile needs to be output.

File formats

There are therefore 2 file formats:

  • .gproof
  • .gprf

Document skeleton files - .gproof

The gproof files are designed to have just enough information in to allow mupdf to mock up pages matching the original PDF file.

Byte Size Content Comments
4 ‘GPRO’ (0x4F525047) File signature
2 Version Currently 1
4 Desired resolution for proofing  
4 n = Number of pages Minimum 1
8*n (Width, Height) pairs for each page (pixels at target resolution) 4 bytes each
variable utf-8 encoded filename of PDF file zero terminated

Page data files - .gprf

This section provides detailed information on the file format that will provide proofing output for overprint, individual separations and color managed viewing as created by Ghostscript for consumption by MuPDF. The format is structured with tiles to enable relatively fast zooming performance in MuPDF and includes an RGB color managed preview, ink levels for individual separations, simple linear mappings from spot levels to RGB and CMYK values as well as an optional ICC profile for the RGB image. The image data is divided into 256x256 tiles, which are compressed. The compression method is defined in the header.

All multi-byte data is encoded as little-endian. All offsets given in the file are offsets from the 0th byte of the File Signature (so if there are multiple pages encoded in a single file, the offsets for the second page start from the second pages header).

The format consists of five primary parts and they occur in the format in the following order:

  • Header (64 bytes)
  • Spot Mappings and Names (Variable # bytes)
  • ICC Profile (Variable # bytes and Optional)
  • Tile Table (Variable # bytes)
  • Tiles (Variable # bytes)

The header details are as follows:

Byte Offset Content Comments
0-3 ‘GSPF’ (0x46505347) File signature
4-5 Version Currently 1
6-7 Compression method of tiles 0x0000 (deflated deltas)
8-11 Width  
12-15 Height  
16-17 Bits/Channel Currently only 8 supported
18-19 Number of separations Minimum 1
20-27 ICC offset Zero if ICC not present
28-35 Table offset  
36-63 Reserved for future Fill with 0s
Following the header, we will have the separation mapping information as well as the separation names. Most commonly, we would have mappings for Cyan, Magenta, Yellow and Black. However, it is possible that there may only be one or two separations. This can occur in mono-tone or duo-tone cases for example. The number of table entries is defined by the number of separations specified in the header. Note that some spot colorants may have opacity properties (e.g. clear varnish). This opacity will be described by the alpha term provided with the RGB values. The RGB values are not pre-multipled by the alpha value. The separation color names are null terminated.

An example is shown below where we have S separations including the standard separations.

Equivalent RGB value (4 bytes) Equivalent CMYK value (4 bytes) UTF-8 Name (at least 1 byte)
RR GG BB AA FF 00 00 00 "Cyan"
RR GG BB AA 00 FF 00 00 "Magenta"
RR GG BB AA 00 00 FF 00 "Yellow"
RR GG BB AA 00 00 00 FF "Black"
RR GG BB AA CC MM YY KK Name of Separation 4
RR GG BB AA CC MM YY KK Name of Separation 5
... ... ...
RR GG BB AA CC MM YY KK Name of Separation S-1
If the ICC offset entry in the header is non-zero, the next part of the file is the ICC profile associated with the RGB data included in the file.

Following the ICC profile data is the tile table. This table contains the index locations of the image tiles. The tiling of the image starts in the lower left indexing to the right (+x) and then up (+y) in chunks of 256x256 with appropriately sized remainders at the image edges. To provide improved compression, each red green and blue channel as well as separation color is individually compressed to provide S+3 separate planes of compressed data for each image tile. In the tile table, the offset locations for each compressed tile location are provided (8 bytes). In the tile table, the individual red, green and blue channel tile locations followed by the separations are provided for the tiles at index [x,y] = [0,0]. The order the separations appear in the table is in the same order as given in the separation color mapping tables. Each table entry is eight bytes. An example table is shown below, where the image has been tiled into W sections along the x direction and H sections along the y direction with S separations:

  Tile Offset
Tile[0,0] Offset Red channel
  Offset Green channel
  Offset Blue channel
  Offset Separation 0
  Offset Separation S-1
Tile[1,0] Offset Red channel
  Offset Green channel
  Offset Blue channel
  Offset Separation 0
  Offset Separation S-1
... ...
Tile[W-1,0] Offset Red channel
  Offset Green channel
  Offset Blue channel
  Offset Separation 0
  Offset Separation S-1
Tile[0,1] Offset Red channel
  Offset Green channel
  Offset Blue channel
  Offset Separation 0
  Offset Separation S-1
... ...
Tile[W-1,H-1] Offset Red channel
  Offset Green channel
  Offset Blue channel
  Offset Separation 0
  Offset Separation S-1
End Offset end of data
The only currently defined compression method for the channel data is defined in the header by the value 0x0000. This indicates that the deflate compression method is used on delta encoded image data. The data is compressed on a stream that consists of concatenated rows running from the top left to the bottom right of the image tile.

For the i'th entry in the offset table, the data is assumed to run to the (i+1)th entry in the table (hence the need for the final end of data offset).

If the length of the channels data thus computed is 0, the data is assumed to be all zeros.

-- Michael Vrhel - 2015-06-16


Edit | Attach | Watch | Print version | History: r14 < r13 < r12 < r11 < r10 | Backlinks | Raw View | Raw edit | More topic actions
Topic revision: r14 - 2017-04-03 - TravisMoore
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2014 Artifex Software Inc