Fork me on GitHub
#membrane
<
2023-10-12
>
pez15:10:33

Hi! I am running into problems trying to style som text with skia.membrane.paragraph , or if it is saving/drawing it to an image. All I get is a white rectangle. Correctly sized, afaict. Here’s what I am trying with:

(defn- create-text [text-width title author description]
  (let [style #:text-style {;:color [1 1 1]
                            :font-families ["NimbusSans" "Menlo"]
                            :font-size 32}]
    (para/paragraph [{:text (str title "\n")
                      :style (merge style #:text-style {:weight :bold})}
                     {:text (str author "\n")
                      :style (merge style #:text-style {:slant :italic})}
                     {:text description
                      :style style}]
                    text-width)))

(comment
  (def text (create-text 1160 "ImageMagick + Pango + Babashka = ♥️"
                              "Peter Strömberg"
                              "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."))
  (java2d/save-image "sometext.png" text (ui/bounds text))
  :rcf)
What am I doing wrong? Note that I’ve commented out the :color style. Because I thought that if the rectangle is white, then drawing white text might be one of the problems. But It is all a white filled, empty, rectangle anyway,

pez15:10:56

What I really want is white text on a transparent background as an image. Because I am later placing the text image on another image.

phronmophobic16:10:52

Hey! The styled text from membrane.skia.paragraph only works with the skia backend.

phronmophobic16:10:34

If you're on the jvm you can get the skia deps with:

com.phronemophobic.membrane/skialib-macosx-aarch64 {:mvn/version "0.14-beta"}
com.phronemophobic.membrane/skialib-linux-x86-64 {:mvn/version "0.14-beta"}
com.phronemophobic.membrane/skialib-macosx-x86-64 {:mvn/version "0.14-beta"}
You only need the dep that matches your system, but including extra doesn't hurt.

pez16:10:41

Thanks! I’ll try that.

pez16:10:55

I have those.

phronmophobic16:10:11

You should then be able to run things using the membrane.skia namespace instead of the membrane.java2d namespace.

phronmophobic16:10:29

membrane.skia also has save-image

pez17:10:24

Still a white rectangle. But I might be holding it wrong still. Will double check.

phronmophobic17:10:57

I just tried your code and got

phronmophobic17:10:23

If you're switching from the java2d backend to the skia backend, you may need to restart your repl 😕

🙏 1
phronmophobic17:10:52

java.awt doesn't always play well with other libs

phronmophobic17:10:57

Here's the version that gets the bold and italic to work:

(defn- create-text [text-width title author description]
  (let [style #:text-style {;:color [1 1 1]
                            :font-families ["NimbusSans" "Menlo"]
                            :font-size 32}]
    (para/paragraph [{:text (str title "\n")
                      :style (merge style #:text-style {:font-style
                                                        #:font-style {:weight :bold}})}
                     {:text (str author "\n")
                      :style (merge style #:text-style {:font-style
                                                        #:font-style {:slant :italic}})}
                     {:text description
                      :style style}]
                    text-width)))

phronmophobic17:10:36

Here's the code for white text on a transparent background:

(defn- create-text [text-width title author description]
  (let [style #:text-style {:color [1 1 1]
                            :font-families ["NimbusSans" "Menlo"]
                            :font-size 32}]
    (para/paragraph [{:text (str title "\n")
                      :style (merge style #:text-style {:font-style
                                                        #:font-style {:weight :bold}})}
                     {:text (str author "\n")
                      :style (merge style #:text-style {:font-style
                                                        #:font-style {:slant :italic}})}
                     {:text description
                      :style style}]
                    text-width)))

(comment
  (def text (create-text 1160 "ImageMagick + Pango + Babashka = ♥️"
                              "Peter Strömberg"
                              "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."))
  (skia/save-image "sometext.png" text nil
                   nil 0 false
                   )

  (skia/run (fn []
              (ui/fill-bordered
               [0 0 0]
               0
               (create-text 1160 "ImageMagick + Pango + Babashka = ♥️"
                            "Peter Strömberg"
                            "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."))
              ))
  
  :rcf)

phronmophobic17:10:43

By default, skia/save-image will fill a white background since that's what folks usually want, but you can skip the clear step by passing false as the last parameter.

pez17:10:52

Awesome. I’ll restart my repl!

pez18:10:12

Now things work. Thanks! Is there a way I can draw-to-image like with java2d, or shall I just use temp files?

phronmophobic18:10:12

there’s no need to. if you have an element, you can just compose it directly

pez18:10:39

I don’t have an element. I have another image that I want to overlay it on.

pez18:10:07

Or, like so, with the correct font configured:

phronmophobic18:10:11

i’m away from keyboard, but you can get the image by calling ui/image with the path and overlay by putting them both in a vector

pez18:10:41

I’ll try that!

phronmophobic17:10:44

@U0ETXRFEW , If you get a chance, I would love to hear how this experiment turned out for you including the good, bad, or ugly.

pez18:10:03

I never figured out how to compose the image using membrane. Neither how to avoid creating a temporary image using skia. So right now I have this:

(defn- create-text 
  [{:keys [width title author description]}]
  (let [style #:text-style {:color [1 1 1]
                            :font-families ["Nimbus Sans" "Arial"]
                            :font-size 32}]
    (para/paragraph [{:text (str title "\n")
                      :style (merge style #:text-style {:font-style #:font-style {:weight :bold}})}
                     {:text (str author "\n")
                      :style (merge style #:text-style {:font-style #:font-style {:slant :italic}})}
                     {:text description
                      :style style}]
                    width)))

(comment 
  (def text (create-text  {:width 1160
                           :title "ImageMagick + Pango + Babashka = ♥️"
                           :author "Peter Strömberg"
                           :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."}))
  (skia/save-image (str (doto (fs/tmp-path! "rcf" "sometext.png") println)) text nil nil 0 false)
  :rcf)

(defn add-texts! [image slug texts]
  (let [g (.createGraphics image)
        text-padding-w 30
        text-padding-h 20
        margin-bottom 80
        image-height (.getHeight image)
        rect-width (.getWidth image)
        text-width (- rect-width (* text-padding-w 2))
        text (create-text (assoc texts :width text-width))
        text-x text-padding-w
        text-image-path (str (fs/tmp-path! slug "texts.png"))
        _text-image? (skia/save-image text-image-path text nil nil 0 false) ;; TODO: Handle error?
        text-image-file (io/as-file text-image-path)
        text-image (ImageIO/read text-image-file)
        rect-height (+ (.getHeight text-image) (* text-padding-h 2))
        rect-y (- image-height rect-height margin-bottom)
        text-y (+ rect-y text-padding-h)]
    (doto g
      (.setComposite (AlphaComposite/getInstance AlphaComposite/SRC_OVER 0.5))
      (.setColor Color/BLACK)
      (.fillRect 0 rect-y rect-width rect-height)
      (.setComposite (AlphaComposite/getInstance AlphaComposite/SRC_OVER 1))
      (.drawImage text-image text-x text-y nil)
      .dispose)
    image))
Which works, but anyway. 😃

👍 1
phronmophobic18:10:30

Thanks for reporting back. Glad you got it working. I'll write an example with membrane, but tbh, java2d image API isn't so bad.

pez18:10:00

It’s OK. But much easier to “see” what I am drawing with membrane.

👍 1
phronmophobic19:10:03

Here's one other way to write it:

(defn add-texts2 [[image-width image-height] texts]
  (let [text-padding-w 30
        text-padding-h 20
        margin-bottom 80
        image-height image-height
        rect-width image-width
        text-width (- rect-width (* text-padding-w 2))
        text (create-text (assoc texts :width text-width))
        text-x text-padding-w
        
        rect-height (+ (ui/height text) (* text-padding-h 2))
        rect-y (- image-height rect-height margin-bottom)
        text-y (+ rect-y text-padding-h)]
    [(ui/translate
      0 rect-y
      (ui/filled-rectangle [0 0 0 0.5]
                           rect-width
                           rect-height))
     (ui/translate text-x text-y
                   text)]))

(defn compose [background text]
  [background
   (add-texts2 (ui/bounds background)
               {:width 1160
                :title "ImageMagick + Pango + Babashka = ♥️"
                :author "Peter Strömberg"
                :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."})])

(comment
  (skia/save-image "overlay.png"
                   (add-texts2 [800 400]
                               {:width 1160
                                :title "ImageMagick + Pango + Babashka = ♥️"
                                :author "Peter Strömberg"
                                :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."}
                               )
                   nil nil 0 false)


  ;; compose with another image
  (skia/save-image "composite.png"
                   (compose (ui/image "mybackground.png")
                            {:width 1160
                             :title "ImageMagick + Pango + Babashka = ♥️"
                             :author "Peter Strömberg"
                             :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."}
                            )
                   nil nil 0 false)
  
  ,)

phronmophobic19:10:43

Not totally sure that's what you're looking for. Anyway, I'm happy to answer any other questions or do a virtual pair session sometime if you're interested!

pez19:10:32

Looks pretty close to what I’m looking for. 😃 I’m composing an image, so if there is a way to make compose return an image?

pez19:10:14

Or to turn what compose returns to an image works too, of course.

phronmophobic19:10:47

The second save-image in the rcf will save that to disk:

(skia/save-image "composite.png"
                   (compose (ui/image "mybackground.png")
                            {:width 1160
                             :title "ImageMagick + Pango + Babashka = ♥️"
                             :author "Peter Strömberg"
                             :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."}
                            )
                   nil nil 0 false)
  

phronmophobic19:10:23

Or what do you mean by an image?

phronmophobic19:10:33

For the most part, an image is just a rasterized array of pixels, but that's usually not that useful.

phronmophobic19:10:03

It's possible to get the rasterized pixels, but I'm guessing you're looking for something else

pez19:10:46

I want something that I can crop, rescale, save as jpg or png. Stuff like that. java.awt.image has had what I need there so far.

👍 1
pez19:10:53

My ultimate goal here might be interesting…

phronmophobic19:10:24

There are descriptions for most of those operations and I can add any that are missing: • crop -> (ui/scissor-view offset bounds view) • rescale -> (ui/scale sx sy view) • save as png (skia/save-image ...)

pez19:10:33

I’m adding metadata from some blog posts as overlays on the images. Because Twitter/X has stopped showing metadata for links. It only shows an image.

phronmophobic19:10:03

Most everything works on views which are just data descriptions of the graphical elements and transformations. Views can be displayed, measured, inspected, and composed with other views. There are also several drawing implementations based on various different graphics libraries.

pez19:10:04

I’ve been using jpg, because those are often smaller on disk.

phronmophobic19:10:38

My suggestions would be to compose and manipulate views and then write the final result to disk with skia/save-image .

pez19:10:09

I think I can make do with writing a temporary png, and then do a last step reading that png file and converting it to jpg.

phronmophobic19:10:28

Yea, that also works, but I think it's easier to work with views programmatically unless I'm missing something. I would be happy to hop on a call for a bit now or later if you're interested and think that would be helpful.

pez19:10:55

Let’s hop in on a call. 😃

clojure-spin 1
phronmophobic20:10:52

Ok, here's the code I was running locally:

(defn add-texts2 [[image-width image-height] texts]
  (let [text-padding-w 30
        text-padding-h 20
        margin-bottom 80
        image-height image-height
        rect-width image-width
        text-width (- rect-width (* text-padding-w 2))
        text (create-text (assoc texts :width text-width))
        text-x text-padding-w
        
        rect-height (+ (ui/height text) (* text-padding-h 2))
        rect-y (- image-height rect-height margin-bottom)
        text-y (+ rect-y text-padding-h)]
    [(ui/translate
      0 rect-y
      (ui/filled-rectangle [0 0 0 0.5]
                           rect-width
                           rect-height))
     (ui/translate text-x text-y
                   text)]))

(defn compose [background text]
  [background
   (add-texts2 (ui/bounds background)
               {:width 1160
                :title "ImageMagick + Pango + Babashka = ♥️"
                :author "Peter Strömberg"
                :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."})])

(comment
  (skia/save-image "overlay.png"
                   (add-texts2 [800 400]
                               {:width 1160
                                :title "ImageMagick + Pango + Babashka = ♥️"
                                :author "Peter Strömberg"
                                :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."}
                               )
                   nil nil 0 false)


  ;; compose with another image
  (skia/save-image "composite.jpeg"
                   (compose (ui/image "mybackground.png")
                            {:width 1160
                             :title "ImageMagick + Pango + Babashka = ♥️"
                             :author "Peter Strömberg"
                             :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."}
                            )
                   nil nil 85 false)
  
  ,)


(defn crop [img]
  (let [[w h] ( ui/bounds img)]
    [(ui/translate
      0
      (- (* h (/ 1 3)))
      (ui/scissor-view
       [0
        (* h (/ 1 3))]
       [w
        (* h (/ 1 3))]
       img))]))

(defmacro wrap-errors [body]
  `(try
     (ui/try-draw
      ~body
      (fn [draw# e#]
        (draw# (ui/label e#))))
     (catch Exception e#
       (ui/label e#))))

(def mpos (atom nil))
(defn debug []
  (wrap-errors
   (let [size [400 600]]
     (ui/on
      :mouse-move
      (fn [pos]
        (reset! mpos pos)
        nil)
      (ui/vertical-layout
       (ui/label @mpos)
       (crop
        (compose (ui/image "mybackground.png")
                 {:width 1160
                  :title "ImageMagick + Pango + Babashka = ♥️"
                  :author "Peter Strömberg"
                  :description "Where there's a will there's a way. I can now hack on my ImageMagick + Pango script in my Babashka REPL on my Mac."}
                 )) []
       (ui/filled-rectangle [1 0 0]
                            10 10))))))
(comment
  (skia/run
    #'debug)
  (skia/run (constantly
             (ui/label "hello world")))
  ,)

🙏 1
phronmophobic23:10:13

fyi, just released version 0.14.2-beta which fixes the layout issue with scissor-view.

🙏 1