« ^ »
[WIP]

ImageMagicで画像を結合しCloudinaryにアップロードする

所要時間: 約 3分

画像の統合

SVGファイルを統合していこうと思ったが、これが結構難しかった。そこでSVGファイルをPNGに変換し、そのPNGファイルを統合する事にした。

SVGからPNGを生成する

まずSVGファイルからPNGファイルを生成する。

magick convert testing1.svg testing1.png

もう1つの画像も同じように行う。

magick convert testing2.svg testing2.png

testing1.pngとtesting2.pngを生成できた。

2つのPNGを統合し1つのPNGを生成する

magick convert +append testing1.png testing2.png merged.png

画像をアクセス可能な場所に配置する

画像を外部からアクセスさせたい場合、アクセス可能な場所におく必要がある。管理が簡単な所ならどこでも良い。今は画像の管理と配信をCloudinaryによって行っている。そこでCloudinaryに生成したファイルをアップロードする。

Cloudinaryのファイルアップロード時に使う署名を計算する

Cloudinaryにファイルをアップロードする為には、署名を付ける必要がある。この署名はリクエストパラメータと資格情報に基づいて計算する。難しい訳ではないけれども、手順があってややこしい。公式文書にはその手順が掲載されていて、仮の値を使用して計算された署名を例示している1。署名の計算ロジックの正しさについては、それを元に確認できる。

署名の計算は次のロジックで行う。

  1. リクエストに送信するパラメータをキーを使いアルファベット順にソートする。
  2. ソートしたパラメータのキーと値を\=で連結した文字列のリストにする。
  3. 2のリストを & で繋いた文字列を作成する。
  4. 3で作成した文字列の末尾にAPIシークレットを連結した文字列を作成する。
  5. 4の文字列のSHA-1もしくはSHA-256を計算する。これが署名となる。

Emacs Lispでの実装を示す。

(setq cloudinary-current-request-parameter-alist
  `((eager . "w_400,h_300,c_pad|w_260,h_200,c_crop")
    (public_id . "sample_image")
    (api_key . "DUMMY")
    (cloud_name . "DUMMY")
    (resource_type . "DUMMY")
    (file . "DUMMY")
    (timestamp . "1315060510")))

(setq cloudinary-api-secret "abcd")
(secure-hash 'sha1
             (concat
              (string-join
               (mapcar (lambda (item) (format "%s=%s" (car item) (cdr item)))
		       (seq-reduce (lambda (alist sym)
				     (assq-delete-all sym alist))
				   '(api_key cloud_name resource_type file)
				   cloudinary-current-request-parameter-alist))
		       "&")
              cloudinary-api-secret))
bfd09f95f331f558cbd1320e67aa8d488770583e

画像をアップロードする

必要な値を設定する。画像データは ImageMagic で生成した画像をbase64コマンドで符号化したものをコピーした。

(setq cloudinary-current-image-base64-encoded "iVBORw0KGgoAAAANSUhEUgAAAPAAAABIEAAAAAC9N/sVAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAACYktHRP//FKsxzQAAAAd0SU1FB+cIAQIUIS4avlsAAAAQY2FOdgAAAHgAAABIAAAAAAAAAADWiylaAAACjklEQVR42u2dP0tbURiH31sFC6LglquDDgaHoKNDuzl0KOQD5At0UDoIFfspFArOLi6u2pa2DrbQ0H4D0yBO1jEkxUFM5To0GPJPOMm577nvr79HkMQb773PeUg8HvQmShIhwDwJfQIkXYYK/DMfpczWW/kaemhAfBNnfixqDOqb7eTU/dzSwLavc2Ad3ewktu7rGFhPt6X83+RNyzdym0VHkaawSOg5vn1fzqLBYWBwGBgcBgZnfNQd+JwWzF1fTYUcDERfPoPBYWBwGBgcz4H/LMxWh19y7/2J1O9RlZXQg2bK123h6/Hvb8zHvzSG9WxZa6nSvq/HwFq6mont+3oLrKmrl9i+r7fA795r6oo8Hwsb2IovZ9HgMDA4DAzOyGvRg1nck1hiiSUXxRJLTjo+R+37D7cuVv/+7tzHUrX1yFzle+ihsumbYuDip52j1s1an803vV+aq3T/6l/Ji8i1yMnTF+mdKLQvX6LBYWBwGBgcBgaHgcFhYHAYGBwGBoeBwWFgcBgYHAYGh4HBYWBwGBgcBgaHgcFhYHAYGBwGBoeBwWFgcBgYHAYGZ+Q/fM/XpSlNaTY2urfs144P5fbf1oeP20fuNeVb6OHA8x058PnMoC31cr3suLPMX0TJni9fosFhYHAYGBzHwB9U/8mvVNA8GqavY+CXn/WUS4WDNa1jAfu6X9ZDR7lUuHvdPuaXie7tOhdhse87RGAN5U7dsIFt+w4VuB+9lxXaLPob4rCBLftyFg0OA4PDwOCkeJWd3eNd5XcdCks2ffkMBoeBwWFgcBgYHG+BX318Nqt54oeTmkcz7Otv9eVmXU/5ctrfeWP7egysp5yFvFZ8vQbWUc5KXhu+jm8QTaxxD5jk60FHdHVqAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIzLTA4LTAxVDAyOjE3OjI1KzAwOjAwwT4ejgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMy0wOC0wMVQwMjoxNzoyNSswMDowMLBjpjIAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjMtMDgtMDFUMDI6MjA6MzMrMDA6MDAzMs8xAAAANHRFWHRzdmc6Y29tbWVudAAgQ3JlYXRlZCB1c2luZyBLcml0YTogaHR0cHM6Ly9rcml0YS5vcmcge8xMKAAAAABJRU5ErkJggg==")

;; DataURIで指定する場合、特殊文字をエスケープ処理する必要がある
(setq cloudinary-current-image-data-uri
   (url-hexify-string (concat "data:image/png;base64,"
      cloudinary-current-image-base64-encoded)))

(setq cloudinary-current-time-stamp
  (number-to-string (truncate (float-time))))

(setq cloudinary-current-request-parameter-alist
  `((api_key . ,cloudinary-api-key)
    (cloud_name . ,cloudinary-cloud-name)
    (resource_type . "auto")
    (timestamp . ,cloudinary-current-time-stamp)
    (file . ,cloudinary-current-image-data-uri)))

署名を計算する。

(secure-hash 'sha1 ;; sha256 も使える
             (concat
              (string-join
               (mapcar (lambda (item) (format "%s=%s" (car item) (cdr item)))
		       (seq-reduce (lambda (alist sym)
				     (assq-delete-all sym alist))
				   '(api_key cloud_name resource_type file)
				   cloudinary-current-request-parameter-alist))
		       "&")
              cloudinary-api-secret))

アップロードリクエストを送信する。

:CLOUD_NAME := cloudinary-cloud-name
:API_KEY := cloudinary-api-key
:FILE_DATA_URI := cloudinary-current-image-data-uri
:TIMESTAMP := cloudinary-current-time-stamp
:SIG = ここは計算した署名値に書き換える

POST https://api.cloudinary.com/v1_1/:CLOUD_NAME/image/upload

api_key=:API_KEY&timestamp=:TIMESTAMP&file=:FILE_DATA_URI&signature=:SIG
{
  "asset_id": "31fdb2ab651ca5a8dbc580335e4e9ee5",
  "public_id": "mbaiynirv9k067ggm3do",
  "version": 1690887786,
  "version_id": "7ece58a686d79da8d027298660d366f2",
  "signature": "815372a46a6cb219ea21e319ed625cd8b2545278",
  "width": 240,
  "height": 72,
  "format": "png",
  "resource_type": "image",
  "created_at": "2023-08-01T11:03:06Z",
  "tags": [],
  "bytes": 1030,
  "type": "upload",
  "etag": "4394cc4245d599981f95135abe85e619",
  "placeholder": false,
  "url": "http://res.cloudinary.com/XXXXXX/image/upload/v1690887786/mbaiynirv9k067ggm3do.png",
  "secure_url": "https://res.cloudinary.com/XXXXXX/image/upload/v1690887786/mbaiynirv9k067ggm3do.png",
  "folder": "",
  "api_key": "XXXXXXXXXXXXXXX"
}
// POST https://api.cloudinary.com/v1_1/XXXXX/image/upload
// HTTP/1.1 200 OK
// Date: Tue, 01 Aug 2023 11:03:06 GMT
// Content-Type: application/json; charset=utf-8
// Transfer-Encoding: chunked
// Connection: keep-alive
// Vary: Accept-Encoding
// Status: 200 OK
// Cache-Control: max-age=0, private, must-revalidate
// X-XSS-Protection: 1; mode=block
// X-Request-Id: 8ecfbadddc0b6988b270b84dffe63711
// X-UA-Compatible: IE=Edge,chrome=1
// ETag: "d0b437306c8f295cf4c99ab8c90e6404"
// Server: cloudinary
// Request duration: 0.653633s

成功するとJSON形式のBODYのHTTPレスポンスが返される。

一定時間操作が無かったら画像を表示する

この画像は、一定時間操作がなければ表示するようにしたい。素なHTMLにスタイルとJavaScriptを直接記述して実装した。

<img
  id="sponsor-img"
  style="
	 display: none;
         position: absolute;
         top: 50%;
         left: 50%;
         transform: translate(-50%, -50%);
         "
src="https://res.cloudinary.com/symdon/image/upload/v1690887786/mbaiynirv9k067ggm3do.png"
  >
</img>

<script>
let timeoutId = null;

function getSponsorImgDOM() {
    return document.querySelector("img#sponsor-img");
}

function resetTimer() {
    if (timeoutId !== null) {
	getSponsorImgDOM().style.display = "none";
	clearTimeout(timeoutId)
    }
    timeoutId = setTimeout(function () {
	getSponsorImgDOM().style.display = "block";
	
	const imgDOM = getSponsorImgDOM()
	imgDOM.style.display = "block";
    }, 10000)
}

document.addEventListener('mousemove', resetTimer);
document.addEventListener('keypress', resetTimer);
document.addEventListener('touchstart', resetTimer);
resetTimer();
</script>


<p style="background: gray"
   >
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</p>