Go實(shí)現(xiàn)圖片上添加水印的示例代碼
前言
在數(shù)字內(nèi)容日益重要的今天,保護(hù)版權(quán)和標(biāo)識來源變得關(guān)鍵。為圖片添加水印有助于聲明所有權(quán)、提升品牌認(rèn)知度,并防止未經(jīng)授權(quán)的使用。本文將介紹如何用Go語言實(shí)現(xiàn)圖片水印,包括靜態(tài)圖片和帶旋轉(zhuǎn)、傾斜效果的文字水印,幫助您有效保護(hù)數(shù)字內(nèi)容。我們將逐步解析關(guān)鍵步驟,確保清晰易懂。
一、準(zhǔn)備工作
為了順利實(shí)現(xiàn)圖片水印功能,您需要完成以下幾個(gè)準(zhǔn)備步驟:
1.安裝Go語言環(huán)境:確保您的開發(fā)環(huán)境中已經(jīng)安裝了Go語言,并具備基本的Go編程知識。
2.安裝必要的庫:
- golang.org/x/image/draw:支持高質(zhì)量縮放及其他圖像繪制操作。
- github.com/disintegration/imaging:提供簡便的API用于圖像變換,如旋轉(zhuǎn)和傾斜。
3.準(zhǔn)備圖像資源:
- 主圖 (Base Image):這是您想要添加水印的原始圖像。它可以是任何您有權(quán)處理的圖像文件。
- 水印圖 (Watermark Image):這是將被放置在主圖之上的圖像,通常是一個(gè)透明背景的PNG文件,這樣可以確保它不會遮擋主圖的重要細(xì)節(jié)。
確保您擁有上述所有工具和資源后,就可以開始編寫代碼來實(shí)現(xiàn)圖片水印功能了。接下來的章節(jié)將逐步指導(dǎo)您如何加載主圖、應(yīng)用水印圖并保存最終結(jié)果。
二、圖片加水印
2.1 圖片水印
2.1.1 打開主圖
首先,我們需要打開并讀取主圖文件。這一步確保了程序能夠訪問到用戶想要處理的原始圖像。
// 打開主圖文件
mainImageFile, err := os.Open("main.png")
if err != nil {
log.Fatalf("Failed to open main image: %v", err)
}
defer mainImageFile.Close()2.1.2 解碼主圖
接下來,從輸入流中讀取原始圖像并解碼它。如果解碼過程中出現(xiàn)問題,程序?qū)⒎祷劐e(cuò)誤信息。這里我們使用image.Decode函數(shù)自動識別圖像格式。
mainImageFile, err := os.Open("main.png")
if err != nil {
log.Fatalf("Failed to open main image: %v", err)
}
defer mainImageFile.Close()2.1.3 打開水印圖片
然后,我們需要打開水印圖片文件。與主圖類似,我們也需要確保能夠正確讀取和解碼水印圖像。
// 打開水印圖片
watermarkImageFile, err := os.Open("logo.png") // 可以替換為其他圖片文件名
if err != nil {
log.Fatalf("Failed to open watermark image: %v", err)
}
defer watermarkImageFile.Close()2.1.4 解碼水印圖片
接下來,從輸入流中讀取水印圖像并解碼它。如果解碼過程中出現(xiàn)問題,程序?qū)⒎祷劐e(cuò)誤信息。這里我們再次使用image.Decode函數(shù)自動識別圖像格式。
// 解碼水印
watermarkImage, _, err := image.Decode(watermarkImageFile)
if err != nil {
log.Fatalf("Failed to decode watermark image: %v", err)
}2.1.5 計(jì)算縮放比例
為了保證水印不會過于顯眼或遮擋過多內(nèi)容,根據(jù)原始圖像的尺寸計(jì)算水印的最大寬度和高度。通常,我們會設(shè)定最大值為原始圖像寬高的25%。然后基于這些最大值計(jì)算出適當(dāng)?shù)目s放比例。
// 獲取主圖和水印的邊界矩形
mainImageBounds := mainImage.Bounds()
watermarkImageBounds := watermarkImage.Bounds()
// 計(jì)算水印的最大尺寸
maxWatermarkWidth := int(float64(mainImageBounds.Max.X) * 0.25) // 最大寬度為主圖寬度的25%
maxWatermarkHeight := int(float64(mainImageBounds.Max.Y) * 0.25) // 最大高度為主圖高度的25%
// 計(jì)算水印的縮放比例
scale := 1.0
if watermarkImageBounds.Max.X > maxWatermarkWidth || watermarkImageBounds.Max.Y > maxWatermarkHeight {
scale = math.Min(
float64(maxWatermarkWidth)/float64(watermarkImageBounds.Max.X),
float64(maxWatermarkHeight)/float64(watermarkImageBounds.Max.Y),
)
}
// 應(yīng)用縮放比例
watermarkWidth := int(float64(watermarkImageBounds.Max.X) * scale)
watermarkHeight := int(float64(watermarkImageBounds.Max.Y) * scale)2.1.6 創(chuàng)建新的圖像
創(chuàng)建一個(gè)新的RGBA圖像,其大小與原始圖像相同,并將原始圖像復(fù)制到這個(gè)新圖像中。
// 創(chuàng)建一個(gè)新的圖像,大小與主圖相同 resultImage := image.NewRGBA(mainImageBounds) // 將主圖復(fù)制到新圖像中 draw.Draw(resultImage, mainImageBounds, mainImage, mainImageBounds.Min, draw.Src)
2.1.7 縮放水印圖像
根據(jù)前面計(jì)算的縮放比例調(diào)整水印圖像的大小。我們可以使用golang.org/x/image/draw包中的draw.CatmullRom.Scale方法來進(jìn)行高質(zhì)量縮放。
// 創(chuàng)建一個(gè)用于存放縮放后水印的新圖像 resizedWatermarkImage := image.NewRGBA(image.Rect(0, 0, watermarkWidth, watermarkHeight)) // 使用高質(zhì)量縮放算法縮放水印圖像 draw.CatmullRom.Scale(resizedWatermarkImage, resizedWatermarkImage.Bounds(), watermarkImage, watermarkImageBounds, draw.Over, nil)
2.1.8 確定水印位置
根據(jù)用戶提供的參數(shù)確定水印應(yīng)該放置的位置,例如左上角、右上角等。對于每個(gè)預(yù)設(shè)的位置,我們計(jì)算出相應(yīng)的坐標(biāo)點(diǎn)。這里僅給出右下角的例子:
// 引入 position 變量,并賦值為一個(gè)有效的水印位置常量
position := "left_top" // 假設(shè)使用 "left_top" 作為示例
// 計(jì)算水印放置的位置
var watermarkX, watermarkY int
switch position {
case "left_top":
watermarkX = int(float64(mainImageBounds.Max.X) * 0.02) // 2% of the width
watermarkY = int(float64(mainImageBounds.Max.Y) * 0.02) // 2% of the height
case "right_top":
watermarkX = int(float64(mainImageBounds.Max.X)*0.98) - watermarkWidth // 98% of the width minus watermark width
watermarkY = int(float64(mainImageBounds.Max.Y) * 0.02) // 2% of the height
case "left_bottom":
watermarkX = int(float64(mainImageBounds.Max.X) * 0.02) // 2% of the width
watermarkY = int(float64(mainImageBounds.Max.Y)*0.98) - watermarkHeight // 98% of the height minus watermark height
case "right_bottom":
watermarkX = int(float64(mainImageBounds.Max.X)*0.98) - watermarkWidth // 98% of the width minus watermark width
watermarkY = int(float64(mainImageBounds.Max.Y)*0.98) - watermarkHeight // 98% of the height minus watermark height
default:
log.Fatalf("Invalid watermark position: %v", position)
}2.1.9 繪制水印
最后,使用draw.Draw方法將調(diào)整后的水印繪制到新圖像的指定位置。
// 將水印繪制到新圖像的指定位置
draw.Draw(resultImage, image.Rectangle{
Min: image.Point{X: watermarkX, Y: watermarkY},
Max: image.Point{X: watermarkX + watermarkWidth, Y: watermarkY + watermarkHeight},
}, resizedWatermarkImage, image.Point{X: 0, Y: 0}, draw.Over)2.1.10 繪制旋轉(zhuǎn)水?。蛇x)
為了讓水印更加多樣化,可以引入旋轉(zhuǎn)或傾斜的效果。這可以通過創(chuàng)建一個(gè)仿射變換矩陣并應(yīng)用于文字圖像來完成。以下是實(shí)現(xiàn)旋轉(zhuǎn)功能的代碼片段:
// 創(chuàng)建一個(gè)新的圖像,大小與水印相同
rotatedWatermarkImage := image.NewRGBA(resizedWatermarkImage.Bounds())
// 引入 rotation 變量,并賦值為一個(gè)有效的旋轉(zhuǎn)角度(度數(shù))
rotation := 45.0 // 假設(shè)使用 45.0 度作為示例
// 計(jì)算旋轉(zhuǎn)角度的弧度
radians := rotation * math.Pi / 180.0
// 計(jì)算旋轉(zhuǎn)后的中心點(diǎn)
centerX := float64(watermarkWidth) / 2.0
centerY := float64(watermarkHeight) / 2.0
// 遍歷每個(gè)像素點(diǎn)并應(yīng)用旋轉(zhuǎn)
for y := 0; y < watermarkHeight; y++ {
for x := 0; x < watermarkWidth; x++ {
// 將像素點(diǎn)轉(zhuǎn)換為相對于中心點(diǎn)的坐標(biāo)
relX := float64(x) - centerX
relY := float64(y) - centerY
// 應(yīng)用旋轉(zhuǎn)矩陣
newX := relX*math.Cos(radians) - relY*math.Sin(radians)
newY := relX*math.Sin(radians) + relY*math.Cos(radians)
// 將旋轉(zhuǎn)后的坐標(biāo)轉(zhuǎn)換回圖像坐標(biāo)
newX += centerX
newY += centerY
// 如果旋轉(zhuǎn)后的坐標(biāo)在圖像范圍內(nèi),則繪制像素
if newX >= 0 && newX < float64(watermarkWidth) && newY >= 0 && newY < float64(watermarkHeight) {
rotatedWatermarkImage.Set(int(newX), int(newY), resizedWatermarkImage.At(x, y))
}
}
}
// 將旋轉(zhuǎn)后的水印繪制到新圖像的指定位置
draw.Draw(resultImage, image.Rectangle{Min: image.Point{X: watermarkX, Y: watermarkY}, Max: image.Point{X: watermarkX + watermarkWidth, Y: watermarkY + watermarkHeight}}, rotatedWatermarkImage, image.Point{X: 0, Y: 0}, draw.Over)2.1.11 保存結(jié)果圖像
根據(jù)原始圖像的格式(如PNG或JPEG),將帶有水印的新圖像編碼并保存到內(nèi)存中的緩沖區(qū),然后再寫入磁盤。
// 保存結(jié)果圖像到內(nèi)存
var buffer bytes.Buffer
switch fileExtension {
case ".png":
err = png.Encode(&buffer, resultImage)
case ".jpg", ".jpeg":
err = jpeg.Encode(&buffer, resultImage, nil)
default:
log.Fatalf("Unsupported file extension: %v", fileExtension)
}
if err != nil {
log.Fatalf("Failed to encode image: %v", err)
}
// 保存結(jié)果圖像到文件
outputFileName := "output" + fileExtension
outputFile, err := os.Create(outputFileName)
if err != nil {
log.Fatalf("Failed to create output file: %v", err)
}
defer outputFile.Close()
// 將內(nèi)存中的圖像數(shù)據(jù)寫入文件
_, err = buffer.WriteTo(outputFile)
if err != nil {
log.Fatalf("Failed to write to output file: %v", err)
}2.1.12 完整代碼和效果
package main
import (
"bytes"
"golang.org/x/image/draw"
"image"
"image/jpeg"
"image/png"
"log"
"os"
"path/filepath"
)
func main() {
// 打開主圖文件
mainImageFile, err := os.Open("main.png")
if err != nil {
log.Fatalf("Failed to open main image: %v", err)
}
defer mainImageFile.Close()
// 獲取文件擴(kuò)展名
fileExtension := filepath.Ext(mainImageFile.Name())
// 解碼主圖
mainImage, _, err := image.Decode(mainImageFile)
if err != nil {
log.Fatalf("Failed to decode main image: %v", err)
}
// 打開水印圖片
watermarkImageFile, err := os.Open("logo.png") // 你可以將 "logo.png" 替換為 "logo.jpg" 或其他圖片文件名
if err != nil {
log.Fatalf("Failed to open watermark image: %v", err)
}
defer watermarkImageFile.Close()
// 解碼水印
watermarkImage, _, err := image.Decode(watermarkImageFile)
if err != nil {
log.Fatalf("Failed to decode watermark image: %v", err)
}
// 獲取主圖和水印的邊界矩形
mainImageBounds := mainImage.Bounds()
watermarkImageBounds := watermarkImage.Bounds()
// 計(jì)算水印的最大尺寸
maxWatermarkWidth := int(float64(mainImageBounds.Max.X) * 0.20) // 你可以將 "0.20" 替換為 "0.15" 或其他值
maxWatermarkHeight := int(float64(mainImageBounds.Max.Y) * 0.20) // 你可以將 "0.20" 替換為 "0.15" 或其他值
// 計(jì)算水印的縮放比例
watermarkWidth := watermarkImageBounds.Max.X
watermarkHeight := watermarkImageBounds.Max.Y
// 計(jì)算縮放比例
scale := 1.0
if watermarkWidth > maxWatermarkWidth {
scale = float64(maxWatermarkWidth) / float64(watermarkWidth)
}
if watermarkHeight > maxWatermarkHeight {
if scale > float64(maxWatermarkHeight)/float64(watermarkHeight) {
scale = float64(maxWatermarkHeight) / float64(watermarkHeight)
}
}
// 應(yīng)用縮放比例
watermarkWidth = int(float64(watermarkWidth) * scale)
watermarkHeight = int(float64(watermarkHeight) * scale)
// 創(chuàng)建一個(gè)新的圖像,大小與主圖相同
resultImage := image.NewRGBA(mainImageBounds)
// 將主圖復(fù)制到新圖像中
draw.Draw(resultImage, mainImageBounds, mainImage, mainImageBounds.Min, draw.Src)
// 縮放水印圖像
resizedWatermarkImage := image.NewRGBA(image.Rect(0, 0, watermarkWidth, watermarkHeight))
draw.NearestNeighbor.Scale(resizedWatermarkImage, resizedWatermarkImage.Bounds(), watermarkImage, watermarkImageBounds, draw.Over, nil)
// 引入 position 變量,并賦值為一個(gè)有效的水印位置常量
position := "left_top" // 假設(shè)使用 "left_top" 作為示例
// 計(jì)算水印放置的位置
var watermarkX, watermarkY int
switch position {
case "left_top":
watermarkX = int(float64(mainImageBounds.Max.X) * 0.02) // 寬度的2%
watermarkY = int(float64(mainImageBounds.Max.Y) * 0.02) // 高度的2%
case "right_top":
watermarkX = int(float64(mainImageBounds.Max.X)*0.98) - watermarkWidth // 寬度的98%減去水印寬度
watermarkY = int(float64(mainImageBounds.Max.Y) * 0.02) // 高度的2%
case "left_bottom":
watermarkX = int(float64(mainImageBounds.Max.X) * 0.02) // 寬度的2%
watermarkY = int(float64(mainImageBounds.Max.Y)*0.98) - watermarkHeight // 高度的98%減去水印高度
case "right_bottom":
watermarkX = int(float64(mainImageBounds.Max.X)*0.98) - watermarkWidth // 寬度的98%減去水印寬度
watermarkY = int(float64(mainImageBounds.Max.Y)*0.98) - watermarkHeight // 高度的98%減去水印高度
default:
log.Fatalf("Invalid watermark position: %v", position)
}
// 將水印繪制到新圖像的指定位置
draw.Draw(resultImage, image.Rectangle{Min: image.Point{X: watermarkX, Y: watermarkY}, Max: image.Point{X: watermarkX + watermarkWidth, Y: watermarkY + watermarkHeight}}, resizedWatermarkImage, image.Point{X: 0, Y: 0}, draw.Over)
// 保存結(jié)果圖像到內(nèi)存
var buffer bytes.Buffer
switch fileExtension {
case ".png":
err = png.Encode(&buffer, resultImage)
case ".jpg", ".jpeg":
err = jpeg.Encode(&buffer, resultImage, nil)
default:
log.Fatalf("Unsupported file extension: %v", fileExtension)
}
if err != nil {
log.Fatalf("Failed to encode image: %v", err)
}
// 保存結(jié)果圖像到文件
outputFile, err := os.Create("output" + fileExtension)
if err != nil {
log.Fatalf("Failed to create output file: %v", err)
}
defer outputFile.Close() // 添加文件關(guān)閉操作
// 將內(nèi)存中的圖像數(shù)據(jù)寫入文件
_, err = buffer.WriteTo(outputFile)
if err != nil {
log.Fatalf("Failed to write to output file: %v", err)
}
}
總結(jié)
通過以上步驟,我們不僅完成了在圖片上添加靜態(tài)圖片水印的功能實(shí)現(xiàn),還增加了旋轉(zhuǎn)、傾斜的水印功能,使得生成的水印更加多樣化和個(gè)性化。您可以根據(jù)自己的需求進(jìn)一步優(yōu)化代碼,比如支持更多的水印位置選項(xiàng),或者允許用戶上傳自定義水印圖片。希望這篇文章能幫助您理解和實(shí)現(xiàn)這一常見但非常有用的功能。如果您有任何問題或遇到困難,請隨時(shí)查閱相關(guān)文檔或?qū)で笊鐓^(qū)的幫助。
到此這篇關(guān)于Go實(shí)現(xiàn)圖片上添加水印的示例代碼的文章就介紹到這了,更多相關(guān)Go 圖片上添加水印內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go-Web框架中AOP方案的實(shí)現(xiàn)方式
本文主要介紹了Go-Web框架中AOP方案的實(shí)現(xiàn)方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
如何用go-zero 實(shí)現(xiàn)中臺系統(tǒng)
這篇文章主要介紹了如何用go-zero 實(shí)現(xiàn)中臺系統(tǒng),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12

