前言
在數字內容日益重要的今天,保護版權和標識來源變得關鍵。為圖片添加水印有助于聲明所有權、提升品牌認知度,并防止未經授權的使用。本文將介紹如何用Go語言實現圖片水印,包括靜態圖片和帶旋轉、傾斜效果的文字水印,幫助您有效保護數字內容。我們將逐步解析關鍵步驟,確保清晰易懂。
一、準備工作
為了順利實現圖片水印功能,您需要完成以下幾個準備步驟:
1.安裝Go語言環境:確保您的開發環境中已經安裝了Go語言,并具備基本的Go編程知識。
2.安裝必要的庫:
- golang.org/x/image/draw:支持高質量縮放及其他圖像繪制操作。
- github.com/disintegration/imaging:提供簡便的API用于圖像變換,如旋轉和傾斜。
3.準備圖像資源:
- 主圖 (Base Image):這是您想要添加水印的原始圖像。它可以是任何您有權處理的圖像文件。
- 水印圖 (Watermark Image):這是將被放置在主圖之上的圖像,通常是一個透明背景的PNG文件,這樣可以確保它不會遮擋主圖的重要細節。
確保您擁有上述所有工具和資源后,就可以開始編寫代碼來實現圖片水印功能了。接下來的章節將逐步指導您如何加載主圖、應用水印圖并保存最終結果。
二、圖片加水印
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 解碼主圖
接下來,從輸入流中讀取原始圖像并解碼它。如果解碼過程中出現問題,程序將返回錯誤信息。這里我們使用image.Decode函數自動識別圖像格式。
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 解碼水印圖片
接下來,從輸入流中讀取水印圖像并解碼它。如果解碼過程中出現問題,程序將返回錯誤信息。這里我們再次使用image.Decode函數自動識別圖像格式。
// 解碼水印
watermarkImage, _, err := image.Decode(watermarkImageFile)
if err != nil {log.Fatalf("Failed to decode watermark image: %v", err)
}
2.1.5 計算縮放比例
為了保證水印不會過于顯眼或遮擋過多內容,根據原始圖像的尺寸計算水印的最大寬度和高度。通常,我們會設定最大值為原始圖像寬高的25%。然后基于這些最大值計算出適當的縮放比例。
// 獲取主圖和水印的邊界矩形
mainImageBounds := mainImage.Bounds()
watermarkImageBounds := watermarkImage.Bounds()// 計算水印的最大尺寸
maxWatermarkWidth := int(float64(mainImageBounds.Max.X) * 0.25) // 最大寬度為主圖寬度的25%
maxWatermarkHeight := int(float64(mainImageBounds.Max.Y) * 0.25) // 最大高度為主圖高度的25%// 計算水印的縮放比例
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),)
}// 應用縮放比例
watermarkWidth := int(float64(watermarkImageBounds.Max.X) * scale)
watermarkHeight := int(float64(watermarkImageBounds.Max.Y) * scale)
2.1.6 創建新的圖像
創建一個新的RGBA圖像,其大小與原始圖像相同,并將原始圖像復制到這個新圖像中。
// 創建一個新的圖像,大小與主圖相同
resultImage := image.NewRGBA(mainImageBounds)// 將主圖復制到新圖像中
draw.Draw(resultImage, mainImageBounds, mainImage, mainImageBounds.Min, draw.Src)
2.1.7 縮放水印圖像
根據前面計算的縮放比例調整水印圖像的大小。我們可以使用golang.org/x/image/draw包中的draw.CatmullRom.Scale方法來進行高質量縮放。
// 創建一個用于存放縮放后水印的新圖像
resizedWatermarkImage := image.NewRGBA(image.Rect(0, 0, watermarkWidth, watermarkHeight))// 使用高質量縮放算法縮放水印圖像
draw.CatmullRom.Scale(resizedWatermarkImage, resizedWatermarkImage.Bounds(), watermarkImage, watermarkImageBounds, draw.Over, nil)
2.1.8 確定水印位置
根據用戶提供的參數確定水印應該放置的位置,例如左上角、右上角等。對于每個預設的位置,我們計算出相應的坐標點。這里僅給出右下角的例子:
// 引入 position 變量,并賦值為一個有效的水印位置常量
position := "left_top" // 假設使用 "left_top" 作為示例// 計算水印放置的位置
var watermarkX, watermarkY int
switch position {
case "left_top":watermarkX = int(float64(mainImageBounds.Max.X) * 0.02) // 2% of the widthwatermarkY = 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 widthwatermarkY = 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 widthwatermarkY = 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 widthwatermarkY = 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方法將調整后的水印繪制到新圖像的指定位置。
// 將水印繪制到新圖像的指定位置
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 繪制旋轉水印(可選)
為了讓水印更加多樣化,可以引入旋轉或傾斜的效果。這可以通過創建一個仿射變換矩陣并應用于文字圖像來完成。以下是實現旋轉功能的代碼片段:
// 創建一個新的圖像,大小與水印相同
rotatedWatermarkImage := image.NewRGBA(resizedWatermarkImage.Bounds())// 引入 rotation 變量,并賦值為一個有效的旋轉角度(度數)
rotation := 45.0 // 假設使用 45.0 度作為示例// 計算旋轉角度的弧度
radians := rotation * math.Pi / 180.0// 計算旋轉后的中心點
centerX := float64(watermarkWidth) / 2.0
centerY := float64(watermarkHeight) / 2.0// 遍歷每個像素點并應用旋轉
for y := 0; y < watermarkHeight; y++ {for x := 0; x < watermarkWidth; x++ {// 將像素點轉換為相對于中心點的坐標relX := float64(x) - centerXrelY := float64(y) - centerY// 應用旋轉矩陣newX := relX*math.Cos(radians) - relY*math.Sin(radians)newY := relX*math.Sin(radians) + relY*math.Cos(radians)// 將旋轉后的坐標轉換回圖像坐標newX += centerXnewY += centerY// 如果旋轉后的坐標在圖像范圍內,則繪制像素if newX >= 0 && newX < float64(watermarkWidth) && newY >= 0 && newY < float64(watermarkHeight) {rotatedWatermarkImage.Set(int(newX), int(newY), resizedWatermarkImage.At(x, y))}}
}// 將旋轉后的水印繪制到新圖像的指定位置
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 保存結果圖像
根據原始圖像的格式(如PNG或JPEG),將帶有水印的新圖像編碼并保存到內存中的緩沖區,然后再寫入磁盤。
// 保存結果圖像到內存
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)
}// 保存結果圖像到文件
outputFileName := "output" + fileExtension
outputFile, err := os.Create(outputFileName)
if err != nil {log.Fatalf("Failed to create output file: %v", err)
}
defer outputFile.Close()// 將內存中的圖像數據寫入文件
_, err = buffer.WriteTo(outputFile)
if err != nil {log.Fatalf("Failed to write to output file: %v", err)
}
2.1.12 完整代碼和效果
package mainimport ("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()// 獲取文件擴展名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()// 計算水印的最大尺寸maxWatermarkWidth := int(float64(mainImageBounds.Max.X) * 0.20) // 你可以將 "0.20" 替換為 "0.15" 或其他值maxWatermarkHeight := int(float64(mainImageBounds.Max.Y) * 0.20) // 你可以將 "0.20" 替換為 "0.15" 或其他值// 計算水印的縮放比例watermarkWidth := watermarkImageBounds.Max.XwatermarkHeight := watermarkImageBounds.Max.Y// 計算縮放比例scale := 1.0if watermarkWidth > maxWatermarkWidth {scale = float64(maxWatermarkWidth) / float64(watermarkWidth)}if watermarkHeight > maxWatermarkHeight {if scale > float64(maxWatermarkHeight)/float64(watermarkHeight) {scale = float64(maxWatermarkHeight) / float64(watermarkHeight)}}// 應用縮放比例watermarkWidth = int(float64(watermarkWidth) * scale)watermarkHeight = int(float64(watermarkHeight) * scale)// 創建一個新的圖像,大小與主圖相同resultImage := image.NewRGBA(mainImageBounds)// 將主圖復制到新圖像中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 變量,并賦值為一個有效的水印位置常量position := "left_top" // 假設使用 "left_top" 作為示例// 計算水印放置的位置var watermarkX, watermarkY intswitch 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)// 保存結果圖像到內存var buffer bytes.Bufferswitch 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)}// 保存結果圖像到文件outputFile, err := os.Create("output" + fileExtension)if err != nil {log.Fatalf("Failed to create output file: %v", err)}defer outputFile.Close() // 添加文件關閉操作// 將內存中的圖像數據寫入文件_, err = buffer.WriteTo(outputFile)if err != nil {log.Fatalf("Failed to write to output file: %v", err)}
}
2.2 文字水印
敬請期待!!!
總結
通過以上步驟,我們不僅完成了在圖片上添加靜態圖片水印的功能實現,還增加了旋轉、傾斜的水印功能,使得生成的水印更加多樣化和個性化。您可以根據自己的需求進一步優化代碼,比如支持更多的水印位置選項,或者允許用戶上傳自定義水印圖片。希望這篇文章能幫助您理解和實現這一常見但非常有用的功能。如果您有任何問題或遇到困難,請隨時查閱相關文檔或尋求社區的幫助。