mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:59:27 +08:00
170 lines
5.0 KiB
Go
170 lines
5.0 KiB
Go
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ofdgo
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"image"
|
|
|
|
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
|
|
)
|
|
|
|
// imageWithEffects 合并同尺寸二值蒙版和对象透明度,保留原图采样精度及只读像素
|
|
// 入参: ctx 取消上下文,nil使用后台上下文, img 原图, mask 二值蒙版,可为空, alpha 对象透明度,可为空
|
|
// 返回: image.Image 处理后的图片, error 蒙版、解码或取消错误
|
|
func imageWithEffects(ctx context.Context, img, mask image.Image, alpha *int) (image.Image, error) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
a := 255
|
|
if alpha != nil {
|
|
a = clampColor(*alpha)
|
|
}
|
|
if img == nil || mask == nil && a == 255 {
|
|
return img, nil
|
|
}
|
|
source, err := baseimage.PixelDataContext(ctx, img)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bounds := source.Bounds()
|
|
var maskBounds image.Rectangle
|
|
if mask != nil {
|
|
mask, err = baseimage.PixelDataContext(ctx, mask)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
maskBounds = mask.Bounds()
|
|
if bounds.Empty() || bounds.Size() != maskBounds.Size() {
|
|
return nil, fmt.Errorf("image mask dimensions differ from source")
|
|
}
|
|
}
|
|
if baseimage.HighPrecision(source) {
|
|
out := image.NewNRGBA64(bounds)
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
if (x-bounds.Min.X)&255 == 0 {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
opacity := a
|
|
if mask != nil {
|
|
value, err := imageMaskAlpha(mask, maskBounds.Min.X+x-bounds.Min.X, maskBounds.Min.Y+y-bounds.Min.Y)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opacity *= int(value) / 255
|
|
}
|
|
pixel := baseimage.NRGBA64At(source, x, y)
|
|
pixel.A = uint16(uint32(pixel.A) * uint32(opacity) / 255)
|
|
out.SetNRGBA64(x, y, pixel)
|
|
}
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
out := image.NewNRGBA(bounds)
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
row := out.Pix[(y-bounds.Min.Y)*out.Stride : (y-bounds.Min.Y+1)*out.Stride]
|
|
if src, ok := source.(*image.NRGBA); ok {
|
|
offset := src.PixOffset(bounds.Min.X, y)
|
|
copy(row, src.Pix[offset:offset+len(row)])
|
|
} else {
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
if (x-bounds.Min.X)&255 == 0 {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
out.SetNRGBA(x, y, baseimage.NRGBAAt(source, x, y))
|
|
}
|
|
}
|
|
for x := 0; x < bounds.Dx(); x++ {
|
|
if x&255 == 0 {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
opacity := a
|
|
if mask != nil {
|
|
value, err := imageMaskAlpha(mask, maskBounds.Min.X+x, maskBounds.Min.Y+y-bounds.Min.Y)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opacity *= int(value) / 255
|
|
}
|
|
row[x*4+3] = uint8(int(row[x*4+3]) * opacity / 255)
|
|
}
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// imageMaskAlpha 读取二值蒙版,拒绝灰阶、彩色及带透明度的像素
|
|
// 入参: mask 蒙版图像, x 横坐标, y 纵坐标
|
|
// 返回: uint8 透明度,黑色为0、白色为255, error 非二值像素错误
|
|
func imageMaskAlpha(mask image.Image, x, y int) (uint8, error) {
|
|
var r, g, b, a uint32
|
|
if source, ok := mask.(image.RGBA64Image); ok {
|
|
pixel := source.RGBA64At(x, y)
|
|
r, g, b, a = uint32(pixel.R), uint32(pixel.G), uint32(pixel.B), uint32(pixel.A)
|
|
} else {
|
|
r, g, b, a = mask.At(x, y).RGBA()
|
|
}
|
|
if a != 65535 || r != g || r != b || r != 0 && r != 65535 {
|
|
return 0, fmt.Errorf("image mask contains non-binary pixel at (%d, %d)", x, y)
|
|
}
|
|
return uint8(r >> 8), nil
|
|
}
|
|
|
|
// validateImageMask 校验二值蒙版尺寸及像素,不分配合成图像
|
|
// 入参: ctx 取消上下文, mask 蒙版图像, size 原图尺寸
|
|
// 返回: error 解码、尺寸、像素或取消错误
|
|
func validateImageMask(ctx context.Context, mask image.Image, size image.Point) error {
|
|
if mask.Bounds().Size() != size || size.X <= 0 || size.Y <= 0 {
|
|
return fmt.Errorf("image mask dimensions differ from source")
|
|
}
|
|
mask, err := baseimage.PixelDataContext(ctx, mask)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bounds := mask.Bounds()
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
if (x-bounds.Min.X)&255 == 0 {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err := imageMaskAlpha(mask, x, y); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return ctx.Err()
|
|
}
|