mirror of
https://bitbucket.org/s_l_teichmann/mtsatellite
synced 2024-11-08 11:10:27 +01:00
Render transparent tiles if command line flag is set.
This commit is contained in:
parent
2aa9ee0e24
commit
8452a26fcd
|
@ -48,6 +48,7 @@ func order(a, b int) (int, int) {
|
||||||
func createBaseLevel(
|
func createBaseLevel(
|
||||||
address string,
|
address string,
|
||||||
xMin, zMin, xMax, zMax int,
|
xMin, zMin, xMax, zMax int,
|
||||||
|
transparent bool,
|
||||||
colorsFile, outDir string,
|
colorsFile, outDir string,
|
||||||
numWorkers int) (err error) {
|
numWorkers int) (err error) {
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ func createBaseLevel(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
done.Add(1)
|
done.Add(1)
|
||||||
btc := common.NewBaseTileCreator(client, colors, baseDir, false)
|
btc := common.NewBaseTileCreator(client, colors, transparent, baseDir, false)
|
||||||
go createTiles(btc, jobs, &done)
|
go createTiles(btc, jobs, &done)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ func main() {
|
||||||
numWorkers int
|
numWorkers int
|
||||||
skipBaseLevel bool
|
skipBaseLevel bool
|
||||||
skipPyramid bool
|
skipPyramid bool
|
||||||
|
transparent bool
|
||||||
)
|
)
|
||||||
|
|
||||||
flag.IntVar(&port, "port", 6379, "port to of mtredisalize server")
|
flag.IntVar(&port, "port", 6379, "port to of mtredisalize server")
|
||||||
|
@ -39,6 +40,8 @@ func main() {
|
||||||
flag.BoolVar(&skipBaseLevel, "sb", false, "Do not generate base level tiles (shorthand)")
|
flag.BoolVar(&skipBaseLevel, "sb", false, "Do not generate base level tiles (shorthand)")
|
||||||
flag.BoolVar(&skipPyramid, "skip-pyramid", false, "Do not generate pyramid tiles")
|
flag.BoolVar(&skipPyramid, "skip-pyramid", false, "Do not generate pyramid tiles")
|
||||||
flag.BoolVar(&skipPyramid, "sp", false, "Do not generate pyramid tiles (shorthand)")
|
flag.BoolVar(&skipPyramid, "sp", false, "Do not generate pyramid tiles (shorthand)")
|
||||||
|
flag.BoolVar(&transparent, "transparent", false, "Render transparent blocks.")
|
||||||
|
flag.BoolVar(&transparent, "t", false, "Render transparent blocks (shorthand).")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -46,7 +49,12 @@ func main() {
|
||||||
if !skipBaseLevel {
|
if !skipBaseLevel {
|
||||||
address := fmt.Sprintf("%s:%d", host, port)
|
address := fmt.Sprintf("%s:%d", host, port)
|
||||||
if err = createBaseLevel(
|
if err = createBaseLevel(
|
||||||
address, xMin, zMin, xMax, zMax, colorsFile, outDir, numWorkers); err != nil {
|
address,
|
||||||
|
xMin, zMin, xMax, zMax,
|
||||||
|
transparent,
|
||||||
|
colorsFile,
|
||||||
|
outDir,
|
||||||
|
numWorkers); err != nil {
|
||||||
log.Fatalf("Creating base level tiles failed: %s", err)
|
log.Fatalf("Creating base level tiles failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ func main() {
|
||||||
colorsfile string
|
colorsfile string
|
||||||
outfile string
|
outfile string
|
||||||
shaded bool
|
shaded bool
|
||||||
|
transparent bool
|
||||||
)
|
)
|
||||||
|
|
||||||
flag.IntVar(&port, "port", 6379, "port to of mtredisalize server")
|
flag.IntVar(&port, "port", 6379, "port to of mtredisalize server")
|
||||||
|
@ -41,6 +42,7 @@ func main() {
|
||||||
flag.StringVar(&outfile, "output", "out.png", "image file of result")
|
flag.StringVar(&outfile, "output", "out.png", "image file of result")
|
||||||
flag.StringVar(&outfile, "o", "out.png", "image file of result (shorthand)")
|
flag.StringVar(&outfile, "o", "out.png", "image file of result (shorthand)")
|
||||||
flag.BoolVar(&shaded, "shaded", true, "draw relief")
|
flag.BoolVar(&shaded, "shaded", true, "draw relief")
|
||||||
|
flag.BoolVar(&transparent, "transparent", false, "render transparent blocks")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -70,7 +72,7 @@ func main() {
|
||||||
q1x, q1y, q1z := int16(x), int16(y), int16(z)
|
q1x, q1y, q1z := int16(x), int16(y), int16(z)
|
||||||
q2x, q2y, q2z := q1x+int16(width)-1, q1y+int16(depth)-1, q1z+int16(height)-1
|
q2x, q2y, q2z := q1x+int16(width)-1, q1y+int16(depth)-1, q1z+int16(height)-1
|
||||||
|
|
||||||
renderer := common.NewRenderer(width, height)
|
renderer := common.NewRenderer(width, height, transparent)
|
||||||
renderer.SetPos(q1x, q1z)
|
renderer.SetPos(q1x, q1z)
|
||||||
yOrder := common.NewYOrder(renderer, 512)
|
yOrder := common.NewYOrder(renderer, 512)
|
||||||
|
|
||||||
|
@ -106,7 +108,7 @@ func main() {
|
||||||
if shaded {
|
if shaded {
|
||||||
image = renderer.CreateShadedImage(
|
image = renderer.CreateShadedImage(
|
||||||
16, 16, (width-2)*16, (height-2)*16,
|
16, 16, (width-2)*16, (height-2)*16,
|
||||||
colors.Colors, color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
colors, color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
||||||
} else {
|
} else {
|
||||||
image = renderer.CreateImage(
|
image = renderer.CreateImage(
|
||||||
colors.Colors, color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
colors.Colors, color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
||||||
|
|
|
@ -25,6 +25,7 @@ func main() {
|
||||||
redisHost string
|
redisHost string
|
||||||
colorsFile string
|
colorsFile string
|
||||||
workers int
|
workers int
|
||||||
|
transparent bool
|
||||||
)
|
)
|
||||||
flag.IntVar(&webPort, "web-port", 8808, "port of the web server")
|
flag.IntVar(&webPort, "web-port", 8808, "port of the web server")
|
||||||
flag.IntVar(&webPort, "p", 8808, "port of the web server (shorthand)")
|
flag.IntVar(&webPort, "p", 8808, "port of the web server (shorthand)")
|
||||||
|
@ -41,6 +42,8 @@ func main() {
|
||||||
flag.IntVar(&workers, "workers", 1, "number of workers to render tiles")
|
flag.IntVar(&workers, "workers", 1, "number of workers to render tiles")
|
||||||
flag.StringVar(&colorsFile, "colors", "colors.txt", "colors used to render map tiles.")
|
flag.StringVar(&colorsFile, "colors", "colors.txt", "colors used to render map tiles.")
|
||||||
flag.StringVar(&colorsFile, "c", "colors.txt", "colors used to render map tiles (shorthand).")
|
flag.StringVar(&colorsFile, "c", "colors.txt", "colors used to render map tiles (shorthand).")
|
||||||
|
flag.BoolVar(&transparent, "transparent", false, "Render transparent blocks.")
|
||||||
|
flag.BoolVar(&transparent, "t", false, "Render transparent blocks (shorthand).")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -56,7 +59,7 @@ func main() {
|
||||||
log.Fatalf("ERROR: problem loading colors: %s", err)
|
log.Fatalf("ERROR: problem loading colors: %s", err)
|
||||||
}
|
}
|
||||||
redisAddress := fmt.Sprintf("%s:%d", redisHost, redisPort)
|
redisAddress := fmt.Sprintf("%s:%d", redisHost, redisPort)
|
||||||
tu := newTileUpdater(mapDir, redisAddress, colors, workers)
|
tu := newTileUpdater(mapDir, redisAddress, colors, transparent, workers)
|
||||||
go tu.doUpdates()
|
go tu.doUpdates()
|
||||||
router.Path("/update").Methods("POST").Handler(tu)
|
router.Path("/update").Methods("POST").Handler(tu)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ type tileUpdater struct {
|
||||||
redisAddress string
|
redisAddress string
|
||||||
colors *common.Colors
|
colors *common.Colors
|
||||||
workers int
|
workers int
|
||||||
|
transparent bool
|
||||||
cond *sync.Cond
|
cond *sync.Cond
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
@ -56,12 +57,18 @@ func (c xz) parent() xzm {
|
||||||
Mask: 1 << (zr<<1 | xr)}
|
Mask: 1 << (zr<<1 | xr)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTileUpdater(mapDir, redisAddress string, colors *common.Colors, workers int) *tileUpdater {
|
func newTileUpdater(
|
||||||
|
mapDir, redisAddress string,
|
||||||
|
colors *common.Colors,
|
||||||
|
transparent bool,
|
||||||
|
workers int) *tileUpdater {
|
||||||
|
|
||||||
tu := tileUpdater{
|
tu := tileUpdater{
|
||||||
mapDir: mapDir,
|
mapDir: mapDir,
|
||||||
redisAddress: redisAddress,
|
redisAddress: redisAddress,
|
||||||
changes: map[xz]bool{},
|
changes: map[xz]bool{},
|
||||||
colors: colors,
|
colors: colors,
|
||||||
|
transparent: transparent,
|
||||||
workers: workers}
|
workers: workers}
|
||||||
tu.cond = sync.NewCond(&tu.mu)
|
tu.cond = sync.NewCond(&tu.mu)
|
||||||
return &tu
|
return &tu
|
||||||
|
@ -113,7 +120,7 @@ func (tu *tileUpdater) doUpdates() {
|
||||||
log.Printf("WARN: Cannot connect to redis server: %s", err)
|
log.Printf("WARN: Cannot connect to redis server: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
btc := common.NewBaseTileCreator(client, tu.colors, baseDir, true)
|
btc := common.NewBaseTileCreator(client, tu.colors, tu.transparent, baseDir, true)
|
||||||
done.Add(1)
|
done.Add(1)
|
||||||
go updateBaseTiles(jobs, btc, &done)
|
go updateBaseTiles(jobs, btc, &done)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,9 +54,10 @@ type BaseTileCreator struct {
|
||||||
func NewBaseTileCreator(
|
func NewBaseTileCreator(
|
||||||
client *RedisClient,
|
client *RedisClient,
|
||||||
colors *Colors,
|
colors *Colors,
|
||||||
|
transparent bool,
|
||||||
baseDir string,
|
baseDir string,
|
||||||
update bool) *BaseTileCreator {
|
update bool) *BaseTileCreator {
|
||||||
renderer := NewRenderer(tileWidth, tileHeight)
|
renderer := NewRenderer(tileWidth, tileHeight, transparent)
|
||||||
return &BaseTileCreator{
|
return &BaseTileCreator{
|
||||||
client: client,
|
client: client,
|
||||||
colors: colors,
|
colors: colors,
|
||||||
|
@ -118,7 +119,7 @@ func (btc *BaseTileCreator) CreateTile(x, z int16, i, j int) error {
|
||||||
|
|
||||||
image := btc.renderer.CreateShadedImage(
|
image := btc.renderer.CreateShadedImage(
|
||||||
16, 16, (tileWidth-2)*16, (tileHeight-2)*16,
|
16, 16, (tileWidth-2)*16, (tileHeight-2)*16,
|
||||||
btc.colors.Colors, color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
btc.colors, color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
||||||
|
|
||||||
path := filepath.Join(btc.baseDir, strconv.Itoa(i), fmt.Sprintf("%d.png", j))
|
path := filepath.Join(btc.baseDir, strconv.Itoa(i), fmt.Sprintf("%d.png", j))
|
||||||
|
|
||||||
|
|
|
@ -115,8 +115,8 @@ func (colors *Colors) BlendColors(span *Span, col color.RGBA, pos int32) color.R
|
||||||
}
|
}
|
||||||
const scale = float32(1) / float32(100)
|
const scale = float32(1) / float32(100)
|
||||||
for ; curr != nil; curr = curr.Next {
|
for ; curr != nil; curr = curr.Next {
|
||||||
// At least 20% attenuation + 5% extra for each depth meter.
|
// At least 35% attenuation + 5% extra for each depth meter.
|
||||||
factor := float32(min(100, 20+(curr.To-curr.From)*5)) * scale
|
factor := float32(min(100, 35+(curr.To-curr.From)*5)) * scale
|
||||||
col = BlendColor(colors.Colors[curr.Value], col, factor)
|
col = BlendColor(colors.Colors[curr.Value], col, factor)
|
||||||
}
|
}
|
||||||
return col
|
return col
|
||||||
|
|
|
@ -27,6 +27,8 @@ type Renderer struct {
|
||||||
RejectedBlocks int
|
RejectedBlocks int
|
||||||
SolidBlocks int
|
SolidBlocks int
|
||||||
TransparentBlocks int
|
TransparentBlocks int
|
||||||
|
spans *SpanPool
|
||||||
|
tBuffer []*Span
|
||||||
}
|
}
|
||||||
|
|
||||||
type YOrder struct {
|
type YOrder struct {
|
||||||
|
@ -122,19 +124,29 @@ func (yo *YOrder) Pop() (x interface{}) {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRenderer(width, height int) (renderer *Renderer) {
|
func NewRenderer(width, height int, transparent bool) (renderer *Renderer) {
|
||||||
dim := width * height
|
dim := width * height
|
||||||
pixSize := dim * 16 * 16
|
pixSize := dim * 16 * 16
|
||||||
yBuffer := make([]int32, pixSize)
|
yBuffer := make([]int32, pixSize)
|
||||||
cBuffer := make([]int32, pixSize)
|
cBuffer := make([]int32, pixSize)
|
||||||
yMin := make([]int32, dim)
|
yMin := make([]int32, dim)
|
||||||
|
|
||||||
|
var tBuffer []*Span
|
||||||
|
var spans *SpanPool
|
||||||
|
|
||||||
|
if transparent {
|
||||||
|
tBuffer = make([]*Span, pixSize)
|
||||||
|
spans = NewSpanPool()
|
||||||
|
}
|
||||||
|
|
||||||
renderer = &Renderer{
|
renderer = &Renderer{
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
yBuffer: yBuffer,
|
yBuffer: yBuffer,
|
||||||
cBuffer: cBuffer,
|
cBuffer: cBuffer,
|
||||||
yMin: yMin}
|
yMin: yMin,
|
||||||
|
tBuffer: tBuffer,
|
||||||
|
spans: spans}
|
||||||
|
|
||||||
renderer.Reset()
|
renderer.Reset()
|
||||||
return
|
return
|
||||||
|
@ -153,6 +165,15 @@ func (r *Renderer) Reset() {
|
||||||
for i, n := 0, len(r.yMin); i < n; i++ {
|
for i, n := 0, len(r.yMin); i < n; i++ {
|
||||||
r.yMin[i] = math.MinInt32
|
r.yMin[i] = math.MinInt32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.tBuffer != nil {
|
||||||
|
for i, t := range r.tBuffer {
|
||||||
|
if t != nil {
|
||||||
|
r.spans.FreeAll(t)
|
||||||
|
r.tBuffer[i] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) IsFilled() bool {
|
func (r *Renderer) IsFilled() bool {
|
||||||
|
@ -189,17 +210,42 @@ func (r *Renderer) RenderBlock(block *Block, colors *Colors) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.Transparent {
|
|
||||||
r.TransparentBlocks++
|
|
||||||
} else {
|
|
||||||
r.SolidBlocks++
|
|
||||||
}
|
|
||||||
|
|
||||||
w := r.width << 4
|
w := r.width << 4
|
||||||
ofs := int(bz)*w<<4 + int(bx)<<4
|
ofs := int(bz)*w<<4 + int(bx)<<4
|
||||||
yB := r.yBuffer
|
yB := r.yBuffer
|
||||||
|
|
||||||
yMin := int32(math.MaxInt32)
|
yMin := int32(math.MaxInt32)
|
||||||
|
|
||||||
|
if db.Transparent && r.tBuffer != nil {
|
||||||
|
r.TransparentBlocks++
|
||||||
|
|
||||||
|
for z := 0; z < 16; z++ {
|
||||||
|
for x := 0; x < 16; x++ {
|
||||||
|
currentY := yB[ofs]
|
||||||
|
if currentY < blockY {
|
||||||
|
for y := 15; y >= 0; y-- {
|
||||||
|
if c, ok := db.Content(x, y, z); ok {
|
||||||
|
currentY = blockY + int32(y)
|
||||||
|
|
||||||
|
if colors.IsTransparent(c) {
|
||||||
|
r.tBuffer[ofs] = r.spans.Insert(r.tBuffer[ofs], currentY, c)
|
||||||
|
} else {
|
||||||
|
r.cBuffer[ofs] = c
|
||||||
|
yB[ofs] = currentY
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if currentY < yMin {
|
||||||
|
yMin = currentY
|
||||||
|
}
|
||||||
|
ofs++
|
||||||
|
}
|
||||||
|
ofs += w - 16
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
r.SolidBlocks++
|
||||||
for z := 0; z < 16; z++ {
|
for z := 0; z < 16; z++ {
|
||||||
for x := 0; x < 16; x++ {
|
for x := 0; x < 16; x++ {
|
||||||
currentY := yB[ofs]
|
currentY := yB[ofs]
|
||||||
|
@ -220,6 +266,8 @@ func (r *Renderer) RenderBlock(block *Block, colors *Colors) (err error) {
|
||||||
}
|
}
|
||||||
ofs += w - 16
|
ofs += w - 16
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r.yMin[pos] = yMin
|
r.yMin[pos] = yMin
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -349,13 +397,15 @@ func safeColor(x int32) uint8 {
|
||||||
|
|
||||||
func (r *Renderer) CreateShadedImage(
|
func (r *Renderer) CreateShadedImage(
|
||||||
xOfs, zOfs, width, height int,
|
xOfs, zOfs, width, height int,
|
||||||
colors []color.RGBA, background color.RGBA) *image.RGBA {
|
cols *Colors, background color.RGBA) *image.RGBA {
|
||||||
|
|
||||||
image := image.NewRGBA(image.Rect(0, 0, width, height))
|
image := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
|
||||||
pw := r.width << 4
|
pw := r.width << 4
|
||||||
|
|
||||||
ofs, numCols := zOfs*pw+xOfs, int32(len(colors))
|
cs := cols.Colors
|
||||||
|
|
||||||
|
ofs, numCols := zOfs*pw+xOfs, int32(len(cs))
|
||||||
|
|
||||||
stride := pw - width
|
stride := pw - width
|
||||||
|
|
||||||
|
@ -365,6 +415,7 @@ func (r *Renderer) CreateShadedImage(
|
||||||
|
|
||||||
pix := image.Pix
|
pix := image.Pix
|
||||||
|
|
||||||
|
if r.tBuffer != nil { // Fast path for transparent images.
|
||||||
for z := height - 1; z >= 0; z-- {
|
for z := height - 1; z >= 0; z-- {
|
||||||
for x := 0; x < width; x++ {
|
for x := 0; x < width; x++ {
|
||||||
colIdx := r.cBuffer[ofs]
|
colIdx := r.cBuffer[ofs]
|
||||||
|
@ -390,7 +441,54 @@ func (r *Renderer) CreateShadedImage(
|
||||||
if d > 36 {
|
if d > 36 {
|
||||||
d = 36
|
d = 36
|
||||||
}
|
}
|
||||||
col := colors[colIdx]
|
col := cs[colIdx]
|
||||||
|
col = color.RGBA{
|
||||||
|
R: safeColor(int32(col.R) + d),
|
||||||
|
G: safeColor(int32(col.G) + d),
|
||||||
|
B: safeColor(int32(col.B) + d),
|
||||||
|
A: 0xff}
|
||||||
|
if r.tBuffer[ofs] != nil {
|
||||||
|
col = cols.BlendColors(r.tBuffer[ofs], col, y)
|
||||||
|
}
|
||||||
|
pix[iofs] = col.R
|
||||||
|
pix[iofs+1] = col.G
|
||||||
|
pix[iofs+2] = col.B
|
||||||
|
pix[iofs+3] = col.A
|
||||||
|
}
|
||||||
|
iofs += 4
|
||||||
|
ofs++
|
||||||
|
}
|
||||||
|
ofs += stride
|
||||||
|
iofs -= istride
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // Solid images.
|
||||||
|
for z := height - 1; z >= 0; z-- {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
colIdx := r.cBuffer[ofs]
|
||||||
|
if colIdx < 0 || colIdx >= numCols {
|
||||||
|
pix[iofs] = background.R
|
||||||
|
pix[iofs+1] = background.G
|
||||||
|
pix[iofs+2] = background.B
|
||||||
|
pix[iofs+3] = 0xff
|
||||||
|
} else {
|
||||||
|
var y, y1, y2 int32
|
||||||
|
y = r.yBuffer[ofs]
|
||||||
|
if x == 0 {
|
||||||
|
y1 = y
|
||||||
|
} else {
|
||||||
|
y1 = r.yBuffer[ofs-1]
|
||||||
|
}
|
||||||
|
if z == 0 {
|
||||||
|
y2 = y
|
||||||
|
} else {
|
||||||
|
y2 = r.yBuffer[ofs+pw]
|
||||||
|
}
|
||||||
|
d := ((y - y1) + (y - y2)) * 12
|
||||||
|
if d > 36 {
|
||||||
|
d = 36
|
||||||
|
}
|
||||||
|
col := cs[colIdx]
|
||||||
pix[iofs] = safeColor(int32(col.R) + d)
|
pix[iofs] = safeColor(int32(col.R) + d)
|
||||||
pix[iofs+1] = safeColor(int32(col.G) + d)
|
pix[iofs+1] = safeColor(int32(col.G) + d)
|
||||||
pix[iofs+2] = safeColor(int32(col.B) + d)
|
pix[iofs+2] = safeColor(int32(col.B) + d)
|
||||||
|
@ -402,5 +500,6 @@ func (r *Renderer) CreateShadedImage(
|
||||||
ofs += stride
|
ofs += stride
|
||||||
iofs -= istride
|
iofs -= istride
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user