mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
1 parent
331e26b71e
commit
16b09e52e4
5 files changed
+194
-33
No files matched your search
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/xiaoqidun/guigo v0.0.0-20261017142315-0279420b6e12
|
github.com/xiaoqidun/guigo v0.0.0-20261017142315-0279420b6e12
|
||||||
github.com/xiaoqidun/j2kgo v1.0.2
|
github.com/xiaoqidun/j2kgo v1.0.2
|
||||||
github.com/xiaoqidun/jbig2 v1.1.8
|
github.com/xiaoqidun/jbig2 v1.1.8
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261016003351-18348c283ac1
|
github.com/xiaoqidun/pdfgo v0.0.0-20261017161421-0b88b77eefc9
|
||||||
golang.org/x/crypto v0.58.0
|
golang.org/x/crypto v0.58.0
|
||||||
golang.org/x/image v0.47.0
|
golang.org/x/image v0.47.0
|
||||||
golang.org/x/text v0.43.0
|
golang.org/x/text v0.43.0
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ github.com/xiaoqidun/j2kgo v1.0.2 h1:OAjjEOfaBaQzBNcj9wx+eM68UI5pewR6SzNZa1R1DIw
|
|||||||
github.com/xiaoqidun/j2kgo v1.0.2/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
|
github.com/xiaoqidun/j2kgo v1.0.2/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.8 h1:dERPW7F8gs18a/DOD9gXHR3fP1Vl6KhCNsLnq5l3BIo=
|
github.com/xiaoqidun/jbig2 v1.1.8 h1:dERPW7F8gs18a/DOD9gXHR3fP1Vl6KhCNsLnq5l3BIo=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.8/go.mod h1:9jromqVwMqlptUDEt6Yip8vqYA5I1YjhG7FgMl/Z2dA=
|
github.com/xiaoqidun/jbig2 v1.1.8/go.mod h1:9jromqVwMqlptUDEt6Yip8vqYA5I1YjhG7FgMl/Z2dA=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261016003351-18348c283ac1 h1:PNucIHG+qid5/GCN3wUGEqfsyrykhoazBbtPaGysWHk=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261017161421-0b88b77eefc9 h1:DsWSdFwsDpI8k0jadqcnGbpJuOpT+tIVIX5JfDFS2GQ=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261016003351-18348c283ac1/go.mod h1:AaabzsObZ8Z76AcaWbo6swRROrJsblyIGB7HbieeJA4=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261017161421-0b88b77eefc9/go.mod h1:AaabzsObZ8Z76AcaWbo6swRROrJsblyIGB7HbieeJA4=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xiaoqidun/ofdgo"
|
"github.com/xiaoqidun/ofdgo"
|
||||||
@@ -406,6 +407,7 @@ type annotationOptions struct {
|
|||||||
// outputContext 定期让出浏览器线程,将取消请求传入库层长操作
|
// outputContext 定期让出浏览器线程,将取消请求传入库层长操作
|
||||||
type outputContext struct {
|
type outputContext struct {
|
||||||
context.Context
|
context.Context
|
||||||
|
mu sync.Mutex
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
checkpoint func() error
|
checkpoint func() error
|
||||||
checked time.Time
|
checked time.Time
|
||||||
@@ -7250,6 +7252,8 @@ func (c *outputContext) Err() error {
|
|||||||
if err := c.Context.Err(); err != nil {
|
if err := c.Context.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
if c.pending != 0 {
|
if c.pending != 0 {
|
||||||
c.pending--
|
c.pending--
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ package webui
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -39,6 +41,9 @@ const (
|
|||||||
platformTypeFunction
|
platformTypeFunction
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// nativeFields 缓存可直接展开的JSON字段,空值使用标准编码
|
||||||
|
var nativeFields sync.Map
|
||||||
|
|
||||||
// NativeResolver 将授权标识解析为随机读取源,生命周期由宿主管理
|
// NativeResolver 将授权标识解析为随机读取源,生命周期由宿主管理
|
||||||
// 入参: token 资源授权标识
|
// 入参: token 资源授权标识
|
||||||
// 返回: io.ReaderAt 随机读取器, int64 资源大小, error 解析错误
|
// 返回: io.ReaderAt 随机读取器, int64 资源大小, error 解析错误
|
||||||
@@ -111,6 +116,13 @@ type platformValue struct{ value any }
|
|||||||
// nativeCallback 同步宿主回调
|
// nativeCallback 同步宿主回调
|
||||||
type nativeCallback func(...any) (any, error)
|
type nativeCallback func(...any) (any, error)
|
||||||
|
|
||||||
|
// nativeField 保存字段名称、位置及空值省略规则
|
||||||
|
type nativeField struct {
|
||||||
|
name string
|
||||||
|
index int
|
||||||
|
omitEmpty bool
|
||||||
|
}
|
||||||
|
|
||||||
// NewNativeEngine 创建独立业务会话,resolver不接收未经授权的文件路径
|
// NewNativeEngine 创建独立业务会话,resolver不接收未经授权的文件路径
|
||||||
func NewNativeEngine(resolve NativeResolver) *NativeEngine { return &NativeEngine{Resolve: resolve} }
|
func NewNativeEngine(resolve NativeResolver) *NativeEngine { return &NativeEngine{Resolve: resolve} }
|
||||||
|
|
||||||
@@ -322,6 +334,26 @@ func nativePlain(value any) any {
|
|||||||
return nativePlain(v.Elem().Interface())
|
return nativePlain(v.Elem().Interface())
|
||||||
}
|
}
|
||||||
if v.Kind() == reflect.Struct {
|
if v.Kind() == reflect.Struct {
|
||||||
|
if fields := nativeStructFields(v.Type()); fields != nil {
|
||||||
|
result := make(map[string]any, len(fields))
|
||||||
|
for _, field := range fields {
|
||||||
|
item := v.Field(field.index)
|
||||||
|
if field.omitEmpty {
|
||||||
|
switch item.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
if item.Len() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if item.Kind() != reflect.Struct && item.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[field.name] = nativePlain(item.Interface())
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
data, err := json.Marshal(value)
|
data, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -360,9 +392,84 @@ func nativePlain(value any) any {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 {
|
||||||
|
if number := v.Float(); math.IsNaN(number) || math.IsInf(number, 0) {
|
||||||
|
_, err := json.Marshal(value)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nativeStructFields 按类型复用普通结构体字段,自定义编码和嵌入字段交给JSON处理
|
||||||
|
// 入参: value 结构体类型
|
||||||
|
// 返回: []nativeField 字段列表,nil表示使用标准编码
|
||||||
|
func nativeStructFields(value reflect.Type) []nativeField {
|
||||||
|
if fields, ok := nativeFields.Load(value); ok {
|
||||||
|
return fields.([]nativeField)
|
||||||
|
}
|
||||||
|
var fields []nativeField
|
||||||
|
if nativePlainType(value, make(map[reflect.Type]bool)) {
|
||||||
|
fields = make([]nativeField, 0, value.NumField())
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
field := value.Field(i)
|
||||||
|
if !field.IsExported() || field.Tag.Get("json") == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, options, _ := strings.Cut(field.Tag.Get("json"), ",")
|
||||||
|
if name == "" {
|
||||||
|
name = field.Name
|
||||||
|
}
|
||||||
|
fields = append(fields, nativeField{name: name, index: i, omitEmpty: options == "omitempty"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stored, _ := nativeFields.LoadOrStore(value, fields)
|
||||||
|
return stored.([]nativeField)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nativePlainType 检查类型是否可直接展开,递归引用及复杂字段沿用标准编码
|
||||||
|
// 入参: value 字段类型, visiting 当前类型路径
|
||||||
|
// 返回: bool 是否可直接展开
|
||||||
|
func nativePlainType(value reflect.Type, visiting map[reflect.Type]bool) bool {
|
||||||
|
if visiting[value] || value.Implements(reflect.TypeFor[json.Marshaler]()) || value.Implements(reflect.TypeFor[encoding.TextMarshaler]()) || reflect.PointerTo(value).Implements(reflect.TypeFor[json.Marshaler]()) || reflect.PointerTo(value).Implements(reflect.TypeFor[encoding.TextMarshaler]()) || value == reflect.TypeFor[json.Number]() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
visiting[value] = true
|
||||||
|
defer delete(visiting, value)
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Pointer, reflect.Slice, reflect.Array:
|
||||||
|
return nativePlainType(value.Elem(), visiting)
|
||||||
|
case reflect.Struct:
|
||||||
|
names := make(map[string]bool)
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
field := value.Field(i)
|
||||||
|
if field.Anonymous {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !field.IsExported() || field.Tag.Get("json") == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, options, _ := strings.Cut(field.Tag.Get("json"), ",")
|
||||||
|
if name == "" {
|
||||||
|
name = field.Name
|
||||||
|
}
|
||||||
|
if name == "-" || names[name] || options != "" && options != "omitempty" || !nativePlainType(field.Type, visiting) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, char := range name {
|
||||||
|
if char != '_' && char != '-' && (char < 'a' || char > 'z') && (char < 'A' || char > 'Z') && (char < '0' || char > '9') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names[name] = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// nativeFailure 转换结构化业务错误
|
// nativeFailure 转换结构化业务错误
|
||||||
func nativeFailure(err error) map[string]any {
|
func nativeFailure(err error) map[string]any {
|
||||||
result := nativePlain(failureResult(err)).(map[string]any)
|
result := nativePlain(failureResult(err)).(map[string]any)
|
||||||
@@ -693,7 +800,8 @@ func (c *nativeCall) finish(name string, result map[string]any) error {
|
|||||||
}
|
}
|
||||||
c.output = nil
|
c.output = nil
|
||||||
} else {
|
} else {
|
||||||
data["blob"] = append([]byte(nil), c.buffer.Bytes()...)
|
data["blob"] = c.buffer.Bytes()
|
||||||
|
c.buffer = bytes.Buffer{}
|
||||||
}
|
}
|
||||||
data["size"] = c.size
|
data["size"] = c.size
|
||||||
c.committed = true
|
c.committed = true
|
||||||
|
|||||||
@@ -21,23 +21,25 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
|
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
// outputImageBatchLimit 限制并行批次持有的原始编码,超限图片单独处理
|
// outputImageBatchLimit 限制并行任务持有的原始编码,超限图片单独处理
|
||||||
const outputImageBatchLimit = 16 << 20
|
const outputImageBatchLimit = 16 << 20
|
||||||
|
|
||||||
// outputImageWorkingSetLimit 限制并行批次的像素工作量估算,超限图片单独处理
|
// outputImageWorkingSetLimit 限制64位并行任务的像素工作量总估算,32位使用64MiB,超限图片单独处理
|
||||||
const outputImageWorkingSetLimit = 64 << 20
|
const outputImageWorkingSetLimit = 256 << 20
|
||||||
|
|
||||||
// outputImageJob 保存独立图片任务,不访问编辑文档或调用进度回调
|
// outputImageJob 保存独立图片任务,不访问编辑文档或调用进度回调
|
||||||
type outputImageJob struct {
|
type outputImageJob struct {
|
||||||
key outputImageKey
|
key outputImageKey
|
||||||
data []byte
|
data []byte
|
||||||
candidate []byte
|
workingSet int
|
||||||
err error
|
candidate []byte
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// outputImageSource 保存已读取资源的摘要和格式,不保留原始编码
|
// outputImageSource 保存已读取资源的摘要和格式,不保留原始编码
|
||||||
@@ -46,6 +48,21 @@ type outputImageSource struct {
|
|||||||
format string
|
format string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// outputImageContext 保留调用方取消检查并支持提前结束并行任务
|
||||||
|
type outputImageContext struct {
|
||||||
|
context.Context
|
||||||
|
parent context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err 检查调用方及本次任务的取消状态
|
||||||
|
// 返回: error 取消错误
|
||||||
|
func (c outputImageContext) Err() error {
|
||||||
|
if err := c.Context.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.parent.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// outputImageOptions 统一资源压缩及像素需求,受保护图片只进行无损编码
|
// outputImageOptions 统一资源压缩及像素需求,受保护图片只进行无损编码
|
||||||
// 入参: key 资源路径
|
// 入参: key 资源路径
|
||||||
// 返回: CompressionOptions 实际策略, image.Point 像素需求
|
// 返回: CompressionOptions 实际策略, image.Point 像素需求
|
||||||
@@ -62,28 +79,52 @@ func (e *Editor) outputImageOptions(key string) (CompressionOptions, image.Point
|
|||||||
return options, demand
|
return options, demand
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareOutputImages 有界并行优化独立图片,读取、缓存及进度仍在调用线程处理
|
// prepareOutputImages 有界并行优化独立图片,完成后补入任务,读取、缓存及进度保持串行
|
||||||
// 入参: keys 资源路径, load 串行读取方法,空值表示跳过, results 候选缓存, sources 原始资源摘要,可为空
|
// 入参: keys 资源路径, load 串行读取方法,空值表示跳过, results 候选缓存, sources 原始资源摘要,可为空
|
||||||
// 返回: error 读取、进度或取消错误
|
// 返回: error 读取、进度或取消错误
|
||||||
func (e *Editor) prepareOutputImages(keys []string, load func(string) ([]byte, error), results map[outputImageKey][]byte, sources map[string]outputImageSource) error {
|
func (e *Editor) prepareOutputImages(keys []string, load func(string) ([]byte, error), results map[outputImageKey][]byte, sources map[string]outputImageSource) error {
|
||||||
workers := min(2, runtime.GOMAXPROCS(0))
|
workers := min(4, runtime.GOMAXPROCS(0))
|
||||||
if workers < 2 || len(keys) < 2 {
|
if workers < 2 || len(keys) < 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
batch := make([]outputImageJob, 0, workers)
|
limit := outputImageWorkingSetLimit
|
||||||
size, workingSet := 0, 0
|
if strconv.IntSize == 32 {
|
||||||
run := func() error {
|
limit = 64 << 20
|
||||||
if err := e.runOutputImages(batch, results); err != nil {
|
}
|
||||||
|
base, cancel := context.WithCancel(e.output.ctx)
|
||||||
|
ctx := outputImageContext{Context: base, parent: e.output.ctx}
|
||||||
|
var group sync.WaitGroup
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
group.Wait()
|
||||||
|
}()
|
||||||
|
completed := make(chan outputImageJob, workers)
|
||||||
|
pending := make(map[outputImageKey]bool)
|
||||||
|
running, size, workingSet := 0, 0, 0
|
||||||
|
finish := func() error {
|
||||||
|
job := <-completed
|
||||||
|
running--
|
||||||
|
size -= len(job.data)
|
||||||
|
workingSet -= job.workingSet
|
||||||
|
delete(pending, job.key)
|
||||||
|
if err := e.output.ctx.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
clear(batch)
|
results[job.key] = nil
|
||||||
batch, size, workingSet = batch[:0], 0, 0
|
if job.err == nil && len(job.candidate) < len(job.data) {
|
||||||
|
results[job.key] = job.candidate
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if err := e.output.ctx.Err(); err != nil {
|
if err := e.output.ctx.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for len(completed) != 0 || running == workers {
|
||||||
|
if err := finish(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := editorProgress(e.OnWriteProgress).withContext(e.output.ctx).report("compress", e.output.completed, 0); err != nil {
|
if err := editorProgress(e.OnWriteProgress).withContext(e.output.ctx).report("compress", e.output.completed, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -105,35 +146,43 @@ func (e *Editor) prepareOutputImages(keys []string, load func(string) ([]byte, e
|
|||||||
if _, cached := results[cacheKey]; cached {
|
if _, cached := results[cacheKey]; cached {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
duplicate := false
|
if pending[cacheKey] {
|
||||||
for _, job := range batch {
|
|
||||||
duplicate = duplicate || job.key == cacheKey
|
|
||||||
}
|
|
||||||
if duplicate {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cost := outputImageWorkingSet(e.output.ctx, data)
|
cost := outputImageWorkingSet(e.output.ctx, data)
|
||||||
if len(batch) != 0 && (len(data) > outputImageBatchLimit-size || cost > outputImageWorkingSetLimit-workingSet) {
|
for running != 0 && (len(data) > outputImageBatchLimit-size || cost > limit-workingSet) {
|
||||||
if err := run(); err != nil {
|
if err := finish(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
batch = append(batch, outputImageJob{key: cacheKey, data: data})
|
job := outputImageJob{key: cacheKey, data: data, workingSet: cost}
|
||||||
|
if len(data) >= outputImageBatchLimit || cost >= limit {
|
||||||
|
if err := e.runOutputImages([]outputImageJob{job}, results); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pending[cacheKey] = true
|
||||||
|
running++
|
||||||
size += len(data)
|
size += len(data)
|
||||||
workingSet += cost
|
workingSet += cost
|
||||||
if len(batch) == workers || size >= outputImageBatchLimit || workingSet >= outputImageWorkingSetLimit {
|
group.Go(func() {
|
||||||
if err := run(); err != nil {
|
job.candidate, job.err = optimizeImageResource(ctx, job.data, job.key.options, job.key.size)
|
||||||
return err
|
completed <- job
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
for running != 0 {
|
||||||
|
if err := finish(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return run()
|
return e.output.ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// outputImageWorkingSet 按原图尺寸估算解码和重采样缓冲,不以降采样目标代替原图
|
// outputImageWorkingSet 按原图尺寸估算解码和重采样缓冲,不以降采样目标代替原图
|
||||||
// 通常预留两份16位RGBA缓冲,TIFF另计分量、瓦片及蒙版存储;格式或尺寸不明时单独处理
|
// 通常预留两份16位RGBA缓冲,TIFF另计分量、瓦片及蒙版存储;格式或尺寸不明时单独处理
|
||||||
// 入参: ctx 取消上下文, data 原始图像编码
|
// 入参: ctx 取消上下文, data 原始图像编码
|
||||||
// 返回: int 编码及像素缓冲的估算字节数,上限为单批预算
|
// 返回: int 编码及像素缓冲的估算字节数,上限为outputImageWorkingSetLimit
|
||||||
func outputImageWorkingSet(ctx context.Context, data []byte) int {
|
func outputImageWorkingSet(ctx context.Context, data []byte) int {
|
||||||
if len(data) >= outputImageWorkingSetLimit {
|
if len(data) >= outputImageWorkingSetLimit {
|
||||||
return outputImageWorkingSetLimit
|
return outputImageWorkingSetLimit
|
||||||
|
|||||||
Reference in new issue
Block a user