1

I'm using a netgear WNDR4300 to run GraphicsMaigick to resize JPEG images. This is the cpuinfo,

cat /proc/cpuinfo
system type             : Atheros AR9344 rev 2
machine                 : NETGEAR WNDR4300
processor               : 0
cpu model               : MIPS 74Kc V4.12
BogoMIPS                : 278.93
wait instruction        : yes
microsecond timers      : yes
tlb_entries             : 32
extra interrupt vector  : yes
hardware watchpoint     : yes, count: 4, address/irw mask: [0x0ffc, 0x0ffc, 0x0ffb, 0x0ffb]
isa                     : mips1 mips2 mips32r1 mips32r2
ASEs implemented        : mips16 dsp dsp2
shadow register sets    : 1
kscratch registers      : 0
package                 : 0
core                    : 0
VCED exceptions         : not available
VCEI exceptions         : not available

It's very slow, and took over 4 minuntes for every photo.

root@OpenWrt:/mnt/sda1/media/# ll
drwxrwxrwx    2 root     root        131072 Jun 10 14:07 ./
drwxrwxrwx    7 root     root        131072 Jun  8 10:18 ../
-rwxrwxrwx    1 root     root       4130026 Feb 10  2018 IMG_20180210_115807.jpg*

root@OpenWrt:/mnt/sda1/media/kidds# time gm mogrify -output-directory > /mnt/sda1/web -resize 64x64 -quality 75 IMG_20170628_100052.jpg
real    4m 19.72s
user    3m 21.86s
sys     0m 9.29s

-resize output

UPDATE:

root@OpenWrt:~# gm identify -version
GraphicsMagick 1.3.31 2018-11-17 Q8 http://www.GraphicsMagick.org/
Copyright (C) 2002-2018 GraphicsMagick Group.
Additional copyrights and licenses apply to this software.
See http://www.GraphicsMagick.org/www/Copyright.html for details.

Feature Support:
  Native Thread Safe       yes
  Large Files (> 32 bit)   yes
  Large Memory (> 32 bit)  no
  BZIP                     no
  DPS                      no
  FlashPix                 no
  FreeType                 yes
  Ghostscript (Library)    no
  JBIG                     no
  JPEG-2000                no
  JPEG                     yes
  Little CMS               no
  Loadable Modules         yes
  OpenMP                   no
  PNG                      yes
  TIFF                     yes
  TRIO                     no
  UMEM                     no
  WebP                     no
  WMF                      no
  X11                      no
  XML                      no
  ZLIB                     yes

Host type: mips-openwrt-linux-gnu

Configured using the command:
  ./configure  '--target=mips-openwrt-linux' '--host=mips-openwrt-linux' '--build=x86_64-pc-linux-gnu' '--program-prefix=' '--program-suffix=' '--prefix=/usr' '--exec-prefix=/usr' '--bindir=/usr/bin' '--sbindir=/usr/sbin' '--libexecdir=/usr/lib' '--sysconfdir=/etc' '--datadir=/usr/share' '--localstatedir=/var' '--mandir=/usr/man' '--infodir=/usr/info' '--disable-nls' '--enable-shared' '--disable-static' '--enable-dependency-tracking' '--with-modules' '--with-threads' '--without-magick-plus-plus' '--without-perl' '--without-bzlib' '--without-dps' '--without-fpx' '--without-jbig' '--without-webp' '--with-jpeg' '--without-jp2' '--without-lcms2' '--without-lzma' '--with-png' '--with-tiff' '--without-trio' '--with-ttf' '--without-umem' '--without-wmf' '--without-xml' '--with-zlib' '--without-zstd' '--without-x' 'build_alias=x86_64-pc-linux-gnu' 'host_alias=mips-openwrt-linux' 'target_alias=mips-openwrt-linux' 'CC=mips-openwrt-linux-musl-gcc' 'CFLAGS=-Os -pipe -mno-branch-likely -mips32r2 -mtune=24kc -fno-caller-save

Final Build Parameters:
  CC       = mips-openwrt-linux-musl-gcc
  CFLAGS   = -Os -pipe -mno-branch-likely -mips32r2 -mtune=24kc -fno-caller-saves -fno-plt -fhonour-copts -Wno-error=unused-but-set-variable -Wno-error=unused-result -msoft-float -mips16 -minterlink-mips16 -iremap/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/build_dir/target-mips_24kc_musl/GraphicsMagick-1.3.31:GraphicsMagick-1.3.31 -Wformat -Werror=format-security -fstack-protector -D_FORTIFY_SOURCE=1 -Wl,-z,now -Wl,-z,relro -flto -Wall
  CPPFLAGS = -I/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/target-mips_24kc_musl/usr/include -I/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/target-mips_24kc_musl/include -I/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/toolchain-mips_24kc_gcc-7.4.0_musl/usr/include -I/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/toolchain-mips_24kc_gcc-7.4.0_musl/include/fortify -I/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/toolchain-mips_24kc_gcc-7.4.0_musl/include -I/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/target-mips_24kc_musl/usr/include/freetype2
  CXX      = mips-openwrt-linux-musl-g++
  CXXFLAGS = -Os -pipe -mno-branch-likely -mips32r2 -mtune=24kc -fno-caller-saves -fno-plt -fhonour-copts -Wno-error=unused-but-set-variable -Wno-error=unused-result -msoft-float -mips16 -minterlink-mips16 -iremap/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/build_dir/target-mips_24kc_musl/GraphicsMagick-1.3.31:GraphicsMagick-1.3.31 -Wformat -Werror=format-security -fstack-protector -D_FORTIFY_SOURCE=1 -Wl,-z,now -Wl,-z,relro -flto
  LDFLAGS  = -L/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/target-mips_24kc_musl/usr/lib -L/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/target-mips_24kc_musl/lib -L/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/toolchain-mips_24kc_gcc-7.4.0_musl/usr/lib -L/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/toolchain-mips_24kc_gcc-7.4.0_musl/lib -znow -zrelro -L/opt/buildbot/slaves/lede-slave-tah/mips_24kc/build/sdk/staging_dir/target-mips_24kc_musl/usr/lib
  LIBS     = -lfreetype -lz -lltdl -lm -lpthread

UPDATE2: sample mode

root@OpenWrt:/mnt/sda1/media/kidds# time gm mogrify -output-directory /mnt/sda1/web -sample 64x64 -quality 75 IMG_20170628_100052.jpg
real    0m 20.76s
user    0m 8.70s
sys     0m 3.29s

-sample output

Is there any way to improve the performance? I just want resize some photoes, if there is some library provided by MIPS, I can write a tool to call it using C language.

culy
  • 193
  • 1
  • 2
  • 8
  • Please click `edit` under your question and add the output from `gm identify -version` and also try changing your output directory to something fast and local, like `/tmp` in case your mounted disk is the cause. – Mark Setchell Jun 10 '19 at 09:54
  • Updated the version result. This device only has 61M /tmp space, GM cannot perform resize on local disk, I set a TMPDIR to usb storage. – culy Jun 10 '19 at 10:29
  • What are the dimensions of your JPEG in pixels wide by pixels tall please - a 4MB JPEG must be pretty enormous! I presume your device is some kind of low power router? If you are doing a lot of this, and it is a router device, maybe you could consider investing in a Raspberry Pi Zero W for $10-15 that has 512MB of RAM and a 1GHz CPU and wifi built-in and use it as a kind of offload engine... – Mark Setchell Jun 10 '19 at 12:11
  • Are you able to compile and run **ImageMagick** instead of **GraphicsMagick**? It is able to use `libjpeg`'s *"shrink-on-load"* feature which significantly reduces the memory required as it subsamples the JPEG coefficients as it reads them from disk. – Mark Setchell Jun 10 '19 at 13:04
  • Alternatively, you can grab the `libjpeg` source from here https://sourceforge.net/projects/libjpeg/ and take the file `example.c` and hack that about to downsample your image to something smaller before calling **GraphicsMagick** to do the final resize and output. You could maybe read 8/16/64 rows at a time (to save memory) and average across that many rows and however many columns and simply write a NetPBM file with say 256x256 pixels and pass that to **GraphicsMagick** for the final resize but which takes far less memory. – Mark Setchell Jun 10 '19 at 13:23
  • Yes, this is a very cheap old-fashioned router. Now there are a lot of powerful options. I am just curious about how much it can do. I want to build a NAS service on it to process photos of my phone, automatically sync photos and generate thumbnails for my private album app. Thanks for your example code, I need some days to try it. – culy Jun 11 '19 at 01:31
  • Could you provide a representative sample image please so I can see the dimensions and the quality. – Mark Setchell Jun 11 '19 at 06:07
  • I added output file in the question, and the source photo is https://drive.google.com/file/d/1TYJ6EtTh1K6Sy-jW-YLF_blltSMHBCnI/view – culy Jun 11 '19 at 10:33
  • I have updated my answer, please have another look. – Mark Setchell Jun 11 '19 at 11:42

1 Answers1

0

I grabbed the file example.c from the v9c source on the IJG website and quickly hacked it about as below to only write every 8th row and every 8th column from your 4640x3480 input file. It creates the following 580x435 image which GraphicsMagick can then presumably manage to resize down to 64x64 in reasonable time since the file is 64x smaller.

enter image description here

You would use:

gm convert result.ppm -resize 64x64 thumb.jpg

It only uses 1.1MB of RAM because it only reads 1 line at a time - I tested using:

/usr/bin/time -l ./example photo.jpg

You could experiment with the decimation factor, I used 8, but you could try 4, or 10 and see the tradeoff between time and quality. Just change the 8 on the second-to-last line of the file.

The code looks like this - I have added "HACK" in the comments so you can see where I was busy making ugly dirty code:

#include <stdio.h>
#include <stdlib.h>
#include "jpeglib.h"
#include <setjmp.h>

struct my_error_mgr {
  struct jpeg_error_mgr pub;    /* "public" fields */

  jmp_buf setjmp_buffer;    /* for return to caller */
};

typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  my_error_ptr myerr = (my_error_ptr) cinfo->err;

  /* Always display the message. */
  /* We could postpone this until after returning, if we chose. */
  (*cinfo->err->output_message) (cinfo);

  /* Return control to the setjmp point */
  longjmp(myerr->setjmp_buffer, 1);
}

GLOBAL(int)
read_JPEG_file (char * filename,int factor)
{
  /* This struct contains the JPEG decompression parameters and pointers to
   * working space (which is allocated as needed by the JPEG library).
   */
  struct jpeg_decompress_struct cinfo;
  /* We use our private extension JPEG error handler.
   * Note that this struct must live as long as the main JPEG parameter
   * struct, to avoid dangling-pointer problems.
   */
  struct my_error_mgr jerr;
  /* More stuff */
  FILE * infile;        /* source file */
  FILE * outfile;       /* result file */        // HACK
  JSAMPARRAY buffer;        /* Output row buffer */
  int row_stride;       /* physical row width in output buffer */

  /* In this example we want to open the input file before doing anything else,
   * so that the setjmp() error recovery below can assume the file is open.
   * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
   * requires it in order to read binary files.
   */

  if ((infile = fopen(filename, "rb")) == NULL) {
    fprintf(stderr, "can't open %s\n", filename);
    return 0;
  }

  /* Step 1: allocate and initialize JPEG decompression object */

  /* We set up the normal JPEG error routines, then override error_exit. */
  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  /* Establish the setjmp return context for my_error_exit to use. */
  if (setjmp(jerr.setjmp_buffer)) {
    /* If we get here, the JPEG code has signaled an error.
     * We need to clean up the JPEG object, close the input file, and return.
     */
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
    return 0;
  }
  /* Now we can initialize the JPEG decompression object. */
  jpeg_create_decompress(&cinfo);

  /* Step 2: specify data source (eg, a file) */

  jpeg_stdio_src(&cinfo, infile);

  /* Step 3: read file parameters with jpeg_read_header() */

  (void) jpeg_read_header(&cinfo, TRUE);
  /* We can ignore the return value from jpeg_read_header since
   *   (a) suspension is not possible with the stdio data source, and
   *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
   * See libjpeg.txt for more info.
   */

  /* Step 4: set parameters for decompression */

  /* In this example, we don't need to change any of the defaults set by
   * jpeg_read_header(), so we do nothing here.
   */

  /* Step 5: Start decompressor */

  (void) jpeg_start_decompress(&cinfo);
  /* We can ignore the return value since suspension is not possible
   * with the stdio data source.
   */

  /* We may need to do some setup of our own at this point before reading
   * the data.  After jpeg_start_decompress() we have the correct scaled
   * output image dimensions available, as well as the output colormap
   * if we asked for color quantization.
   * In this example, we need to make an output work buffer of the right size.
   */ 
  /* JSAMPLEs per row in output buffer */
  row_stride = cinfo.output_width * cinfo.output_components;
  /* Make a one-row-high sample array that will go away when done with image */
  buffer = (*cinfo.mem->alloc_sarray)
        ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

  /* Step 6: while (scan lines remain to be read) */
  /*           jpeg_read_scanlines(...); */

  // Write a simple PPM file called "result.ppm" that can be thumbnailed with GraphicsMagick
  // gm convert result.ppm -resize 64x64 thumbnail.jpg
  if ((outfile = fopen("result.ppm", "wb")) == NULL) {           // HACK
    fprintf(stderr, "can't open result.ppm\n");                  // HACK
    return 0;                                                    // HACK
  }
  int outwidth  = cinfo.output_width/factor;                     // HACK
  int outheight = cinfo.output_height/factor;                    // HACK
  fprintf(outfile,"P6\n%d %d\n255\n",outwidth,outheight);        // HACK
  unsigned char * outbuf;                                        // HACK
  outbuf = (unsigned char*)malloc(outwidth*3);                   // HACK

  int line=0;           // HACK
  while (cinfo.output_scanline < cinfo.output_height) {
    /* jpeg_read_scanlines expects an array of pointers to scanlines.
     * Here the array is only one element long, but you could ask for
     * more than one scanline at a time if that's more convenient.
     */
    (void) jpeg_read_scanlines(&cinfo, buffer, 1);
    // Do every Nth row                                          // HACK
    if(line%factor==0){                                          // HACK
       int pixel,op;
       op=0;
       for(pixel=0;pixel<row_stride/3;pixel+=factor){
          outbuf[op++] = buffer[0][(pixel*3) +0];
          outbuf[op++] = buffer[0][(pixel*3) +1];
          outbuf[op++] = buffer[0][(pixel*3) +2];
       }
       fwrite(outbuf,1,op,outfile);                              // HACK
    }                                                            // HACK
    line++;                                                      // HACK
  }

  /* Step 7: Finish decompression */

  (void) jpeg_finish_decompress(&cinfo);

  /* Step 8: Release JPEG decompression object */

  /* This is an important step since it will release a good deal of memory. */
  jpeg_destroy_decompress(&cinfo);

  fclose(infile);
  return 1;
}

int main(int argc, char*argv[]){

   // Read "test.jpg" and only output every 8th row and every 8th column for a 64x size reduction
   read_JPEG_file ("test.jpg",8); // HACK
}

I compiled it with:

clang -std=c11  $(pkg-config --cflags --libs libjpeg) example.c -o example
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • I updated the question, used -sample parameter, it took 20 seconds, but the resized image quality is very low. Is it do the same thing as you metioned? – culy Jun 11 '19 at 02:50