Can you save a jpeg image?

I need to be able to rescale some jpeg images. The 2htdp/image library has bitmap and scale procedures, which is almost exactly what I need, except I have not found a way to save the image back as a jpeg, only as a png. In fact, I could find no reference in any search that I did to saving jpegs. Is this a deliberate omission to avoid patent issues, or am I (as my wife often maintains) just being blind?

Just to get you started, try the below. Beware, I got something wrong about the color-bytes transformation and ‘red turns into ‘blue. @rfinder may be able to provide an internal way to get this done.

#lang racket

(require 2htdp/image)
(require (only-in racket/draw bitmap%))

#; {2htdp:Image String -> Void}
(define (save-jpeg img name)
  (define width (image-width img))
  (define hight (image-height img))
  (define pixls (apply bytes-append (map color->bytes (image->color-list img))))
  (define bitmp (make-object bitmap% width hight))
  (send bitmp set-argb-pixels 0 0 width hight pixls)
  (send bitmp save-file name 'jpeg))

#; {2hdp:Color -> Byte}
(define (color->bytes clr)
  (match-define (color r g b α) clr)
  (bytes r g b α))

(save-jpeg (rectangle 200 200 'solid 'red) "foo.jpeg”)

Image? is convertible to a pict via Conversion to Picts, Picts can be easily converted to bitmaps via bitmap (pict) or pict->bitmap

Actually the bitmap and pict->bitmap functions from pict also allow pict-convertible? as argument which means that these functions, can be used directly to convert from image to an pict and bitmap% instance respectively.
[The documentation could be improved if there was a listing of all datatypes that are pict-convertible? somewhere]

bitmap% then has the save-file method which supports: png, jpeg, xbm, xpm, bmp

#lang racket

(require racket/contract
         (only-in racket/draw bitmap%)
         (only-in pict pict->bitmap))

(provide (contract-out
          [image->bitmap (-> image? (is-a?/c bitmap%))]
          [save-jpeg     (->* (image? (or/c path-string? output-port?)) ((integer-in 0 100)) boolean?)]))

(define (image->bitmap image)
  ;; could use pict->bitmap directly,
  ;; but maybe you want to use some pict
  ;; functions before converting to bitmap
  ;; (pict->bitmap image)
  (pict->bitmap (pict-convert image)))

(define (save-jpeg image name/outport [quality 95])
  (send (image->bitmap image) save-file name/outport 'jpeg quality))

(module+ main
  (define c (circle 20 "solid" "blue"))
  (save-jpeg c "testjpg.jpg"))

Overall I prefer using pict from the start (e.g. via picts bitmap function) (I rarely use 2htdp/image; and when I do, I tend to convert the images to picts, combining/processing them further with pict operations).
Sometimes I also directly load bitmap%s depending on the problem.

Pict Combiners

You might want to use one of the superimpose variants with a background rectangle for images/picts with transparency to create a background color (if you haven't done something similar with image functions).

(cc-superimpose (filled-rectangle width height #:color "white" #:draw-border? #f)
                (scale-to-fit some-pict width height #:mode 'inset))

file -> 2htdp/Image -> pict -> bitmap% -> file

image -> pict-convert -> pict?
pict-convertible? -> pict->bitmap -> (is-a?/c bitmap%)

(or/c path-string?
      (is-a?/c bitmap%)
      convertible?) -> bitmap (pict) -> pict?
1 Like

Brilliant! Thanks. I went with using pict (rather than 2htdp/image), that with scale-to-fit solved my problem. That said, I found the code supplied by @emef instructive, as it helped clarifying my understanding of what is going on. (Being new to racket, just getting my head around the responses was instructive for me.) So, many thanks to you both.

1 Like

Just found this save-pict in the pict-abbrevs package.
Using this function may be more convenient, if you don't need to specify for example the quality parameter or save to some arbitrary port.