生成帶 LOGO 的二維碼

通過(guò) 生成二維碼之 Java (Google zxing) 篇 我們可以實(shí)現(xiàn)簡(jiǎn)單二維碼的生成, 但是二維碼顯示卻過(guò)于單調(diào), 本文變講述如何利用 thumbnailator 為我們的二維碼添加 LOGO

thumbnailator 是一個(gè)縮略圖工具類庫(kù), 但它除了能縮略圖片外, 還提供裁剪, 旋轉(zhuǎn), 水印等功能, 此次我們便借助它的水印 API 實(shí)現(xiàn)以上需求

項(xiàng)目環(huán)境

<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
</dependency>

操作步驟

  1. 準(zhǔn)備一張二維碼圖片, 一張 logo 圖片

    String qrCodeFilePath = "src/qrCode.jpg";
    String logoFilePath = "src/logo.jpg";
    
src/qrCode.png, 內(nèi)容為 1

src/logo.png
  1. 根據(jù)文件路徑生成一個(gè)圖片構(gòu)造對(duì)象( Thumbnails.of() 方法還可以接收 BufferedImage,InputStream,URL,String 的可變參數(shù)類型)

    Thumbnails.Builder<File> builder = Thumbnails.of(new File(qrCodeFilePath));
    
  2. 創(chuàng)建一個(gè)水印對(duì)象, 水印對(duì)象需要三個(gè)參數(shù) Position position, BufferedImage watermarkImg,float opacity, 其中 Position 是水印的坐標(biāo),BufferedImage 是水印圖片,opacity 是不透明度, 值為 [0-1] 之間, 1 代表不透明.

    BufferedImage bufferedImage = ImageIO.read(new File(logoFilePath));
    //Positions 實(shí)現(xiàn)了 Position 并提供九個(gè)坐標(biāo), 分別是 上左, 上中, 上右, 中左, 中中, 中右, 下左, 下中, 下右 我們使用正中的位置
    Watermark watermark = new Watermark(Positions.CENTER, bufferedImage, 1F);
    
  3. 為二維碼設(shè)置水印, 并設(shè)置縮略比例為 1(即不壓縮), 輸出到一個(gè)新文件中(outputFormat() 為指定輸出格式, 如: jpg,png)

    builder.watermark(watermark).scale(1F).toFile(new File("src/logoQrCode.png"));
    
  4. 生成后的二維碼


    src/logoQrCode.png

通過(guò)以上方法我們能夠簡(jiǎn)單的為二維碼添加 LOGO, 但是實(shí)際使用時(shí)遠(yuǎn)沒(méi)有這樣簡(jiǎn)單, 有以下幾個(gè)問(wèn)題

  1. logo 圖片的尺寸可能并不固定, 可能有大有小, 這樣就會(huì)導(dǎo)致 logo 在二維碼中太小或太大
    這時(shí)我們可以在創(chuàng)建 BufferedImage 時(shí)將原圖壓縮 / 放大至指定尺寸, 也可以使用二維碼的尺寸乘以一定比例

    //forceSize(int width, int height) 指將圖片強(qiáng)制壓縮為指定寬高, 如不強(qiáng)制, 可使用 size(int width, int height)
    BufferedImage bufferedImage = Thumbnails.of(new File(logoFilePath)).forceSize(120, 120).asBufferedImage();
    
  2. 二維碼圖片, logo 圖片可能不是一個(gè)圖片文件, 同時(shí)生成好之后的圖片希望直接輸出在頁(yè)面上
    Thumbnails.of() 可以接收 BufferedImage,InputStream,URL,String,File 的可變 (** 批量處理需要 **) 參數(shù)類型
    ImageIO.read() 可以接收 File,ImageInputStream,InputStream,URL 參數(shù)類型
    以下為 SpringMVC 動(dòng)態(tài)生成帶 LOGO 二維碼示例,QRCodeUtil.toBufferedImage() 具體實(shí)現(xiàn)請(qǐng)看 生成二維碼之 Java (Google zxing) 篇

    /**
     * @param content 二維碼內(nèi)容
     * @param logoUrl logo 鏈接
     */
    @RequestMapping(value = "/qrcode")
    public void qrcode(String content, String logoUrl, @RequestParam(defaultValue = "300") int width, @RequestParam(defaultValue = "300") int height,HttpServletResponse response) {
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            // 根據(jù) QRCodeUtil.toBufferedImage() 返回的 BufferedImage 創(chuàng)建圖片構(gòu)件對(duì)象
            Thumbnails.Builder<BufferedImage> builder = Thumbnails.of(QRCodeUtil.toBufferedImage(content, width, height));
            // 將 logo 的尺寸設(shè)置為二維碼的 30% 大小, 可以自己根據(jù)需求調(diào)節(jié)
            BufferedImage logoImage = Thumbnails.of(new URL(logoUrl)).forceSize((int) (width * 0.3), (int) (height * 0.3)).asBufferedImage();
            // 設(shè)置水印位置(居中), 水印圖片 BufferedImage, 不透明度(1F 代表不透明)
            builder.watermark(Positions.CENTER, logoImage, 1F).scale(1F);
            // 此處需指定圖片格式, 否者報(bào) Output format not specified 錯(cuò)誤
            builder.outputFormat("png").toOutputStream(outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * 返回一個(gè) BufferedImage 對(duì)象
     * @param content 二維碼內(nèi)容
     * @param width   寬
     * @param height  高
     */
    public static BufferedImage toBufferedImage(String content, int width, int height) throws WriterException, IOException {
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
        return MatrixToImageWriter.toBufferedImage(bitMatrix);
    }
    

拓展思考

以上案例可知 thumbnailator 水印功能的強(qiáng)大. 然而除了實(shí)現(xiàn)以上案例外, 我們也可以根據(jù)水印功能實(shí)現(xiàn)另外一種需求.
比如公司現(xiàn)在有一個(gè)推廣需求, 讓用戶為我們的產(chǎn)品進(jìn)行宣傳, 達(dá)到多少次就進(jìn)行獎(jiǎng)勵(lì). 傳統(tǒng)的實(shí)現(xiàn)方式是一段文字再配上一個(gè)專屬鏈接. 但是此方式往往太過(guò)單調(diào), 枯燥. 這個(gè)時(shí)候如果我們讓設(shè)計(jì)提供一張內(nèi)容豐富, 帶有二維碼的宣傳圖片, 然后根據(jù)專屬鏈接生成二維碼與宣傳圖片進(jìn)行組合. 這時(shí)用戶就有了一張自己的專屬宣傳圖片. 此時(shí)通過(guò)直接宣傳專屬圖片往往會(huì)比文字加鏈接有著更好的效果.
注: 此功能在圖片合成時(shí)需要對(duì)二維碼的位置進(jìn)行定位, 此時(shí)可使用 Position 的實(shí)現(xiàn)類 Coordinate 完成
使用如下:

//Coordinate 存在一個(gè) Coordinate(int x, int y) 構(gòu)造函數(shù), x 為水印距離底圖左邊的像素, y 為上邊
BufferedImage bufferedImage = ImageIO.read(new File(logoFilePath));
Watermark watermark = new Watermark(new Coordinate(100, 100), bufferedImage, 1F);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末要糊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子揽碘,更是在濱河造成了極大的恐慌,老刑警劉巖铃彰,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晌区,死亡現(xiàn)場(chǎng)離奇詭異忌傻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)疆虚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門苛败,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人装蓬,你說(shuō)我怎么就攤上這事著拭。” “怎么了牍帚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵儡遮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我暗赶,道長(zhǎng)鄙币,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任蹂随,我火速辦了婚禮十嘿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岳锁。我一直安慰自己绩衷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布激率。 她就那樣靜靜地躺著咳燕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乒躺。 梳的紋絲不亂的頭發(fā)上招盲,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音嘉冒,去河邊找鬼曹货。 笑死,一個(gè)胖子當(dāng)著我的面吹牛讳推,可吹牛的內(nèi)容都是我干的顶籽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼娜遵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜕衡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起设拟,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤慨仿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后纳胧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體镰吆,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年跑慕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了万皿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡核行,死狀恐怖牢硅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芝雪,我是刑警寧澤减余,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站惩系,受9級(jí)特大地震影響位岔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜堡牡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一抒抬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晤柄,春花似錦擦剑、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至浇借,卻和暖如春捉撮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妇垢。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工巾遭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闯估。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓灼舍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涨薪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骑素,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容