轉換 base64 成圖片再轉換成 PDF 的踩坑筆記

March 10, 2018

需求

最近在實作一個功能,主要是將 Canvas 轉換成圖片後,再與給定圖片合併,最後轉換成 PDF。在這之前找了一些相關可以使用的套件,好在已經有人實作過,讓我減少一些在額外開發其他功能的時間。

在實作過程中,我把該套件再透過 Vue 進行一層包裝,讓我在開發可以把它作為元件(Compononet)四處使用,有興趣可以參考 - vue-signature-pad;基本上這個元件的功能主要都是為了系統功能部分需求而去實作的,但是原先套件的重要的核心功能都有加入進來。

嘗試開發

首先,我透過 vue-signature-pad 提供了一個畫板給使用者,使用者繪製完畢儲存後,我會將這個 Canvas 畫板的內容轉換成圖片並儲存,接著再把它轉成 base64 提供給使用者繪製後的預覽結果,基本上透過 vue-signature-pad 就可以完成這件事情,在 vue-signature-pad README 有提供一個將畫板內容轉換成 base64 的簡單範例:

<template>
  <div id="app">
    <VueSignaturePad
      width="500px"
      height="500px"
      ref="signaturePad"
    />
    <div>
      <button @click="save">Save</button>
      <button @click="undo">Undo</button>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'MySignaturePad',
    methods: {
      undo() {
        this.$refs.signaturePad.undoSignature();
      },
      save() {
        const { isEmpty, data } = this.$refs.signaturePad.saveSignature();
        console.log(isEmpty);
        console.log(data);
      }
    }
  }
</script>

接下來會把 base64 的資料送到後端,把他轉換後儲存成圖片,一開始是使用 Intervention/image 這個 library 來處理,因為剛好系統就有使用到這個套件,處理的方式為:

$base64Image = $request->input('b64Img');
$image = Image::make($base64Image)->stream('png');
Storage::disk('local')->put('your_specified_path'.'/sampleImage.png', $image);

將傳入的 base64 透過 Image::make() 再轉成 stream,接著透過 Store$image 內容放入儲存到指定的位置,在操作以上過程後,圖片可以成功的被儲存。

問題

本來以為把圖片儲存後,在與指定的圖片做合併轉換成 PDF 檔案應該就沒什麼問題了,於是就按照這個想法繼續往下實作。

關於圖片轉換成 PDF 檔案,我是使用 imagemagick 來處理的,這裡就不多闡述。

在合併轉換 PDF 完成後,開心地打開 PDF 檔案,結果發現:

Failure PDF

怎麼會第一頁和第二頁的大小不一致!事先準備好的圖片大小是一樣的,照理來說不應該出現這樣的情況,於是我開始回頭檢查整個流程:

    1. 檢查 Canvas 轉成 base64 的結果
    1. 檢查 Image 轉換 base64 成圖片的結果
    1. 檢查 imagemagick 合併過程

流程檢查完後的結果看起來都沒問題,但是轉出來的 PDF 一直有問題,我一直覺得是在 imagemagick 出了問題,於是我另外重新合併指定圖片成兩頁,結果是正確的

於是我回頭再去檢查步驟二圖片的尺寸大小,也是跟原先的尺寸一樣,看起來沒問題,可以推理到第一步的結果也是沒問題的,思考了許久後來發覺問題還是可能出在步驟二,畢竟透過了 library 來幫我做了轉換,也不太確定這轉換的中間 library 會不會有其他的處理,於是先把 Image 給拿掉,透過 PHP 內建函式來作轉換:

$base64Image = $request->input('b64Img');
$image = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $base64Image));
Storage::disk('local')->put('your_specified_path'.'/sampleImage.png', $image);

再重新跑整個流程一次,最後打開 PDF 的結果是 - 正確

沒想到問題竟然是出在 Image 上,本來以為是因為把它轉換成 stearm() 造成的問題,後來嘗試修改成 Image::make($base64Image)->encode('png')->encoded 結果還是跟原先一樣,不確定 library 內部還有沒有做了其他事情造成這樣的結果,因為開發時間蠻趕的,沒有特別去深入去追這裡的問題,想說 PHP 內建函式可以處理掉也是可以接受的,之後有時間回頭再來找找好了!

Reference

Link