summaryrefslogtreecommitdiff
path: root/demos
diff options
context:
space:
mode:
authorMichael Vrhel <michael.vrhel@artifex.com>2022-02-03 14:33:18 -0800
committerMichael Vrhel <michael.vrhel@artifex.com>2022-02-03 14:36:11 -0800
commit4899045d9e98220becf30daafc1daff7dc7d0722 (patch)
tree80cc4676a69d8537581c66e28f3625babd7cea10 /demos
parent6468e0dadf670ae5832d370d888767f2767073a2 (diff)
downloadghostpdl-4899045d9e98220becf30daafc1daff7dc7d0722.tar.gz
MATLAB API demo
Add an example showing how to obtain rendered output directly to images in MATLAB. These images can then be processed by other MATLAB tools such as ocr to analyze text, etc.
Diffstat (limited to 'demos')
-rw-r--r--demos/MATLAB/ghostpdl.m85
-rw-r--r--demos/MATLAB/gs_displaydevice.c514
2 files changed, 599 insertions, 0 deletions
diff --git a/demos/MATLAB/ghostpdl.m b/demos/MATLAB/ghostpdl.m
new file mode 100644
index 000000000..833cf1fdc
--- /dev/null
+++ b/demos/MATLAB/ghostpdl.m
@@ -0,0 +1,85 @@
+% Copyright (C) 2001-2022 Artifex Software, Inc.
+% All Rights Reserved.
+%
+% This software is provided AS-IS with no warranty, either express or
+% implied.
+%
+% This software is distributed under license and may not be copied,
+% modified or distributed except as expressly authorized under the terms
+% of the license contained in the file LICENSE in this distribution.
+%
+% Refer to licensing information at http://www.artifex.com or contact
+% Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato,
+% CA 94945, U.S.A., +1(415)492-9861, for further information.
+%
+%
+% Paths in this example are relative to the MATLAB
+% folder in the ghostscript project. MATLAB only
+% runs on Windows 64 bit machines. Interface is
+% limited to a subset of the API methods due to
+% the fact that the MATLAB API does not allow
+% function pointers. We want to use the display
+% device callbacks to get the image data created
+% from Ghostscript. To do this, we have to wrap the
+% callback set up and associated functions into a
+% MEX-file that will serve as an interface between MATLAB
+% and the Ghostscript DLL. We use the C MEX API (as opposed to
+% the C++ MEX API). The mex file is gs_displaydevice.c
+
+% The creation of the mex file for the display device handling only
+% needs to be done once. Note the use of -R2018a as we are using
+% type-safe data access in the mex file. This also is doing a debug version -g.
+mex 'gs_displaydevice.c' -R2018a -g '../../debugbin/gpdldll64.lib'
+
+% You have to load the library to get the mex file to find the library and
+% to make direct calls to gpdldll64 directly from MATLAB
+if not(libisloaded('gpdldll64'))
+ [nf,warn] = loadlibrary('../../debugbin/gpdldll64.dll','../../pcl/pl/plapi.h')
+end
+
+% Show us the various methods in the DLL
+% libfunctions('gpdldll64')
+
+% Use planar format for MATLAB. See gdevdsp.h for what these bits mean.
+PlanarGray = 0x800802;
+PlanarRGB = 0x800804;
+PlanarCMYK = 0x800808;
+PlanarSpots = 0x880800;
+
+% Let try the display device and return the image
+page_number = 1;
+resolution = 200;
+input_file = '../../examples/tiger.eps';
+tiger_image_rgb = gs_displaydevice(input_file, PlanarRGB, page_number, resolution);
+figure(1);
+imshow(tiger_image_rgb);
+title('RGB rendering');
+
+tiger_image_gray = gs_displaydevice(input_file, PlanarGray, page_number, resolution);
+figure(2);
+imshow(tiger_image_gray);
+title('Gray rendering');
+
+% MATLAB will not display CMYK or NColor Images. We have to show
+% the separations for this case
+tiger_image_cmyk = gs_displaydevice(input_file, PlanarCMYK, page_number, resolution);
+for k=1:4
+ eval(sprintf('figure(2+k);'));
+ imshow(tiger_image_cmyk(:,:,k));
+ switch k
+ case 1
+ title('Cyan Separation');
+ case 2
+ title('Magenta Separation');
+ case 3
+ title('Yellow Separation');
+ case 4
+ title('Black Separation');
+ end
+end
+
+% At this stage, you can push the image data through MATLAB's ocr if you
+% have the Computer Vision Toolbox and want to do some sort of text
+% analysis. You can also use direct calls into gpdldll64. See the
+% C-API demo for examples on what can be done to render to file output or
+% save as PDF, PS, etc. \ No newline at end of file
diff --git a/demos/MATLAB/gs_displaydevice.c b/demos/MATLAB/gs_displaydevice.c
new file mode 100644
index 000000000..cb12c9832
--- /dev/null
+++ b/demos/MATLAB/gs_displaydevice.c
@@ -0,0 +1,514 @@
+/* Copyright (C) 2001-2022 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied,
+ modified or distributed except as expressly authorized under the terms
+ of the license contained in the file LICENSE in this distribution.
+
+ Refer to licensing information at http://www.artifex.com or contact
+ Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato,
+ CA 94945, U.S.A., +1(415)492-9861, for further information.
+*/
+
+#include "mex.h"
+#include <memory.h>
+#include <assert.h>
+
+#ifndef GHOSTPDL
+#define GHOSTPDL 1
+#endif
+
+#if GHOSTPDL
+#include "../../pcl/pl/plapi.h" /* GSAPI - gpdf version */
+#else
+#include "psi/iapi.h" /* GSAPI - ghostscript version */
+#endif
+#include "../../devices/gdevdsp.h"
+/*--------------------------------------------------------------------*/
+/* Much of this set-up is stolen from the C-API demo code */
+
+#ifdef HIDE_POINTERS
+void *hide_pointer(void *p)
+{
+ return (p == NULL) ? NULL : (void *)1;
+}
+
+#define PTR(p) hide_pointer(p)
+
+#else
+
+#define PTR(p) p
+
+#endif
+
+#define PlanarGray 0x800802
+#define PlanarRGB 0x800804
+#define PlanarCMYK 0x800808
+#define PlanarSpots 0x880800
+#define INSTANCE_HANDLE ((void *)1234)
+#define SANITY_CHECK_VALUE 0x12345678
+
+#define SANITY_CHECK(ts) assert(ts->sanity_check_value == SANITY_CHECK_VALUE)
+
+/* All the state for a given test is contained within the following
+ * structure. */
+typedef struct {
+ /* This value should always be set to SANITY_CHECK_VALUE. It
+ * allows us to check we have a valid (or at least plausible)
+ * teststate_t pointer by checking its value. */
+ int sanity_check_value;
+
+ int w;
+ int h;
+ int r;
+ int pr;
+ int format;
+
+ int n;
+ void *mem;
+
+ mxArray **mex_image_data;
+
+} teststate_t;
+
+
+static int
+open(void *handle, void *device)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ mexPrintf("open from C-Mex function\n");
+
+ return 0;
+}
+
+static int
+preclose(void *handle, void *device)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ mexPrintf("preclose from C-Mex function\n");
+
+ return 0;
+}
+
+static int
+close(void *handle, void *device)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ mexPrintf("close from C-Mex function\n");
+
+ return 0;
+}
+
+static int
+presize(void *handle, void *device,
+ int width, int height, int raster, unsigned int format)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ mexPrintf("presize: w=%d h=%d r=%d f=%x\n",
+ width, height, raster, format);
+
+ ts->w = width;
+ ts->h = height;
+ ts->r = raster;
+ ts->format = format;
+
+ if (ts->format & DISPLAY_COLORS_GRAY)
+ ts->n = 1;
+ if (ts->format & DISPLAY_COLORS_RGB)
+ ts->n = 3;
+ if (ts->format & DISPLAY_COLORS_CMYK)
+ ts->n = 4;
+ if (ts->format & DISPLAY_COLORS_SEPARATION)
+ ts->n = 0;
+ if ((ts->format & DISPLAY_DEPTH_MASK) != DISPLAY_DEPTH_8)
+ return -1; /* Haven't written code for that! */
+
+ return 0;
+}
+
+static int
+size(void *handle, void *device, int width, int height,
+ int raster, unsigned int format, unsigned char *pimage)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ mexPrintf("size: w=%d h=%d r=%d f=%x m=%p\n",
+ width, height, raster, format, PTR(pimage));
+
+ ts->w = width;
+ ts->h = height;
+ ts->r = raster;
+ ts->format = format;
+ ts->mem = pimage;
+
+ if (ts->format & DISPLAY_PLANAR)
+ ts->pr = ts->r * height;
+ /* When running with spots, n is not known yet. */
+ if (ts->n != 0 && ts->format & DISPLAY_PLANAR_INTERLEAVED)
+ ts->pr = ts->r / ts->n;
+
+ return 0;
+}
+
+static int
+sync(void *handle, void *device)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+
+ mexPrintf("sync\n");
+
+ return 0;
+}
+
+static int
+page(void *handle, void *device, int copies, int flush)
+{
+ teststate_t *ts = (teststate_t *)handle;
+ mwSize dims[3];
+ int num_planes;
+ int i,j,k;
+ unsigned char *des_ptr, *src_ptr, *matlab_ptr, *row_ptr;
+ size_t matlab_planar_raster;
+
+ SANITY_CHECK(ts);
+
+ mexPrintf("page: c=%d f=%d\n", copies, flush);
+
+ /* Transfer to the mex output variables at this time */
+ switch (ts->format) {
+ case PlanarGray:
+ num_planes = 1;
+ break;
+ case PlanarRGB:
+ num_planes = 3;
+ break;
+ case PlanarCMYK:
+ num_planes = 4;
+ break;
+ case PlanarSpots:
+ num_planes = 4;
+ break;
+ }
+
+ /* Matlab uses a column ordered layout for its storage of
+ arrays. So we need to do a little effort here. */
+ /* Allocate MATLAB outputs */
+ dims[0] = ts->h;
+ dims[1] = ts->w;
+ dims[2] = num_planes;
+ *(ts->mex_image_data) = mxCreateNumericArray(3, dims, mxUINT8_CLASS, mxREAL);
+
+ /* Grab image pointer */
+ matlab_ptr = (unsigned char*) mxGetUint8s(*(ts->mex_image_data));
+ matlab_planar_raster = ts->w * ts->h;
+
+ /* Copy to MATLAB memory */
+ for (k = 0; k < num_planes; k++) {
+ des_ptr = matlab_ptr + k * matlab_planar_raster;
+ src_ptr = (unsigned char*) ts->mem + k * ts->pr;
+ row_ptr = src_ptr;
+ for (i = 0; i < ts->h; i++) {
+ for (j = 0; j < ts->w; j++) {
+ des_ptr[i + j * ts->h] = *row_ptr++;
+ }
+ row_ptr = src_ptr + i * ts->r;
+ }
+ }
+ return 0;
+}
+
+static int
+update(void *handle, void *device, int x, int y, int w, int h)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ /* This print statement just makes too much noise :) */
+ /* mexPrintf("update: x=%d y=%d w=%d h=%d\n", x, y, w, h); */
+
+ return 0;
+}
+
+static void *
+memalloc(void *handle, void *device, size_t size)
+{
+ void *ret = NULL;
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+
+ ret = malloc(size);
+
+ return ret;
+}
+
+static int
+memfree(void *handle, void *device, void *mem)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ mexPrintf("memfree: %p\n", PTR(mem));
+
+ free(mem);
+
+ return 0;
+}
+
+static int
+separation(void *handle, void *device,
+ int component, const char *component_name,
+ unsigned short c, unsigned short m,
+ unsigned short y, unsigned short k)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+
+ mexPrintf("separation: %d %s (%x,%x,%x,%x)\n",
+ component, component_name ? component_name : "<NULL>",
+ c, m, y, k);
+ ts->n++;
+
+ /* Update the plane_raster as n has changed. */
+ if (ts->format & DISPLAY_PLANAR_INTERLEAVED)
+ ts->pr = ts->r / ts->n;
+
+ return 0;
+}
+
+static int
+adjust_band_height(void *handle, void *device, int bandheight)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+ mexPrintf("adjust_band_height: Unsupported!");
+
+ return 0;
+}
+
+static int
+rectangle_request(void *handle, void *device,
+ void **memory, int *ox, int *oy,
+ int *raster, int *plane_raster,
+ int *x, int *y, int *w, int *h)
+{
+ teststate_t *ts = (teststate_t *)handle;
+
+ SANITY_CHECK(ts);
+
+ mexPrintf("rectangle_request: Unsupported!");
+ return 0;
+}
+
+/*--------------------------------------------------------------------*/
+/* All those callback functions live in a display_callback structure
+ * that we return to the main code. This can be done using the modern
+ * "callout" method, or by using the legacy (deprecated) direct
+ * registration method. We strongly prefer the callout method as it
+ * avoids the need to pass a pointer using -sDisplayHandle. */
+static display_callback callbacks =
+{
+ sizeof(callbacks),
+ DISPLAY_VERSION_MAJOR,
+ DISPLAY_VERSION_MINOR,
+ open,
+ preclose,
+ close,
+ presize,
+ size,
+ sync,
+ page,
+ update,
+ memalloc,
+ memfree,
+ separation,
+ adjust_band_height,
+ rectangle_request
+};
+
+/*--------------------------------------------------------------------*/
+/* This is our callout handler. It handles callouts from devices within
+ * Ghostscript. It only handles a single callout, from the display
+ * device, to return the callback handler and callback handle. */
+static int
+callout(void *instance,
+ void *callout_handle,
+ const char *device_name,
+ int id,
+ int size,
+ void *data)
+{
+ teststate_t *ts = (teststate_t *)callout_handle;
+
+ SANITY_CHECK(ts);
+
+ /* We are only interested in callouts from the display device. */
+ if (strcmp(device_name, "display"))
+ return -1;
+
+ if (id == DISPLAY_CALLOUT_GET_CALLBACK)
+ {
+ /* Fill in the supplied block with the details of our callback
+ * handler, and the handle to use. In this instance, the handle
+ * is the pointer to our test structure. */
+ gs_display_get_callback_t *cb = (gs_display_get_callback_t *)data;
+ cb->callback = &callbacks;
+ cb->caller_handle = ts;
+ return 0;
+ }
+ return -1;
+}
+
+/* mexFunction is the gateway routine for the MEX-file. Like the main
+ function in a C project */
+void
+mexFunction( int nlhs, mxArray *plhs[],
+ int nrhs, const mxArray *prhs[] )
+{
+ /* Set up GS to use the display device. Register the callback
+ which will callback to a MATLAB function, whose task it is
+ to deal with the rendered image data */
+ /* Make the teststate a blank slate for us to work with. */
+ teststate_t teststate = { SANITY_CHECK_VALUE };
+
+ /* Construct the argc/argv to pass to ghostscript. */
+ int argc = 0;
+ char *argv[10];
+ char format_arg[64];
+ char first_page_arg[64];
+ char last_page_arg[64];
+ char resolution_arg[64];
+ int code = 0;
+ void *instance = NULL;
+ int format, page_number;
+ size_t m, n;
+ mxDouble *value;
+ int result;
+ char *file_name = NULL;
+ int len;
+ int resolution;
+
+ /* Error checking */
+ if (nrhs != 4) {
+ mexPrintf("Incorrect number of input args");
+ return;
+ }
+
+ if (mxGetClassID(prhs[0]) != mxCHAR_CLASS || mxIsComplex(prhs[0]) ||
+ mxGetM(prhs[0]) != 1) {
+ mexErrMsgTxt("First input must be file name");
+ return;
+ }
+ len = mxGetN(prhs[0]);
+ file_name = (char *)malloc(len + 1);
+ if (file_name == NULL) {
+ mexErrMsgTxt("Filename allocation failed");
+ return;
+ }
+ code = mxGetString(prhs[0], file_name, len + 1);
+ if (code != 0) {
+ mexErrMsgTxt("Mex string copy failed");
+ return;
+ }
+
+ if (mxGetClassID(prhs[1]) != mxUINT32_CLASS || mxIsComplex(prhs[1]) ||
+ mxGetN(prhs[1]) * mxGetM(prhs[1]) != 1) {
+ mexErrMsgTxt("Second input must be data format");
+ return;
+ }
+ format = mxGetScalar(prhs[1]);
+
+ if (!mxIsDouble(prhs[2]) || mxIsComplex(prhs[2]) ||
+ mxGetN(prhs[2]) * mxGetM(prhs[2]) != 1) {
+ mexErrMsgTxt("Third input must be page number");
+ return;
+ }
+ page_number = mxGetScalar(prhs[2]);
+
+ if (!mxIsDouble(prhs[3]) || mxIsComplex(prhs[3]) ||
+ mxGetN(prhs[3]) * mxGetM(prhs[3]) != 1) {
+ mexErrMsgTxt("Fourth input must be resolution");
+ return;
+ }
+ resolution = mxGetScalar(prhs[3]);
+
+ if (!(format == PlanarGray || format == PlanarRGB ||
+ format == PlanarCMYK || format == PlanarSpots)) {
+ mexPrintf("Data format must be planar");
+ return;
+ }
+
+ if (nlhs != 1) {
+ mexPrintf("Incorrect number of output args");
+ return;
+ }
+
+ /* Set up command */
+ argv[argc++] = "gs";
+ argv[argc++] = "-sDEVICE=display"; /* Using display device to get callback*/
+ argv[argc++] = "-dNOPAUSE";
+ argv[argc++] = first_page_arg;
+ argv[argc++] = last_page_arg;
+ argv[argc++] = format_arg;
+ argv[argc++] = resolution_arg;
+
+ sprintf(first_page_arg, "-dFirstPage=%d", page_number);
+ sprintf(last_page_arg, "-dLastPage=%d", page_number);
+ sprintf(format_arg, "-dDisplayFormat=16#%x", format);
+ sprintf(resolution_arg, "-r%d", resolution);
+
+ argv[argc++] = file_name;
+
+ /* Create a GS instance. */
+ code = gsapi_new_instance(&instance, INSTANCE_HANDLE);
+ if (code < 0) {
+ mexPrintf("Error %d in gsapi_new_instance\n", code);
+ return;
+ }
+
+ /* Assign lhs variables to teststate members */
+ teststate.mex_image_data = &plhs[0];
+
+ /* Register our callout handler. This will pass the display
+ * device the callback structure and handle when requested. */
+ code = gsapi_register_callout(instance, callout, &teststate);
+ if (code < 0) {
+ mexPrintf("Error %d in gsapi_register_callout\n", code);
+ goto fail;
+ }
+
+ code = gsapi_init_with_args(instance, argc, argv);
+ if (code < 0) {
+ mexPrintf("Error %d in gsapi_init_with_args\n", code);
+ goto fail;
+ }
+
+ /* Close the interpreter down (important, or we will leak!) */
+ code = gsapi_exit(instance);
+ if (code < 0) {
+ mexPrintf("Error %d in gsapi_exit\n", code);
+ }
+
+fail:
+ /* Delete the gs instance. */
+ gsapi_delete_instance(instance);
+
+ if (file_name != NULL)
+ free(file_name);
+}