package d2raster

import (
	
	
	
	

	
	
)

// RenderBands renders a frame from top to bottom using a reusable canvas of at
// most bandHeight rows. Each NRGBA band has absolute frame coordinates, full
// frame width, and consecutive rows. Its pixels are borrowed only until consume
// returns; a consumer that retains a band must copy it.
//
// Geometry and assets are prepared once. All resource limits, including the
// aggregate scanline and even-odd work of every band and filter overlap, are
// checked before the first callback. MaxPixels still bounds total output pixels;
// only the band canvas, rather than the full frame, must fit platform storage.
// MaxOffscreenBytes separately bounds retained pattern tiles and rendering
// scratch. The final band may have fewer rows. On any error, consume receives no
// further bands. A callback must not modify document or its assets.
func ( context.Context,  *d2scene.Document,  FrameOptions,  int,  func(*image.NRGBA) error) error {
	return renderBands(, , , nil, , )
}

// RenderBands reuses the session's bounded font and raster-asset cache while
// rendering consecutive borrowed bands. Its contract is otherwise RenderBands'.
func ( *RenderSession) ( context.Context,  *d2scene.Document,  FrameOptions,  int,  func(*image.NRGBA) error) error {
	if  == nil {
		return fmt.Errorf("d2raster: nil render session")
	}
	return renderBands(, , , , , )
}

func renderBands( context.Context,  *d2scene.Document,  FrameOptions,  *RenderSession,  int,  func(*image.NRGBA) error) error {
	if  <= 0 {
		return fmt.Errorf("d2raster: band height must be positive")
	}
	if  == nil {
		return fmt.Errorf("d2raster: nil band consumer")
	}
	if  == nil {
		 = context.Background()
	}
	,  := prepareWithSessionBands(, , , , )
	if  != nil {
		return 
	}
	if  := .Err();  != nil {
		return 
	}
	 := &rasterScratch{
		regionFilters:     true,
		rasterizerEdges:   .resources.rasterizerEdges,
		rasterizerBounded: true,
		offscreen:         offscreenBudget{limit: .MaxOffscreenBytes},
		scanlineWork:      scanline.NewWorkBudget(.resources.scanlineWork),
		scanlineWorkSet:   true,
	}
	defer .releasePatternTiles()
	,  := .offscreen.reserveBytes(.resources.rasterizerBytes, "scanline rasterizer working storage")
	if  != nil {
		return 
	}
	defer .offscreen.release()
	for ,  := range .patterns {
		if  := .render(, );  != nil {
			return 
		}
	}

	 = min(, .height)
	 := image.NewRGBA(image.Rect(0, 0, .width, ))
	for  := 0;  < .height; {
		if  := .Err();  != nil {
			return 
		}
		 :=  + min(, .height-)
		.Rect = image.Rect(0, , .width, )
		.Pix = .Pix[:.Stride*(-)]
		if .background != nil && .background.A == 0xff {
			fillOpaquePixels(.Pix, *.background)
		} else if .background != nil && .background.A != 0 {
			draw.Draw(, .Bounds(), image.NewUniform(*.background), image.Point{}, draw.Src)
		} else {
			clear(.Pix)
		}
		if  := renderNode(, , .root, );  != nil {
			return 
		}
		 :=  + .patternBytes
		if .offscreen.live !=  {
			return fmt.Errorf("d2raster: internal band offscreen reservation leak: %d bytes live, want %d", .offscreen.live, )
		}
		if .offscreen.peak > .resources.peakOffscreenBytes {
			return fmt.Errorf("d2raster: internal band offscreen peak %d exceeds preflight plan %d", .offscreen.peak, .resources.peakOffscreenBytes)
		}
		 := .Bounds()
		if .background == nil || .background.A == 0 {
			 = .root.bounds.Intersect(.Bounds())
		}
		,  := rgbaToNRGBAInPlaceBounds(, , , .background != nil && .background.A == 0xff)
		if  != nil {
			return 
		}
		if  := .Err();  != nil {
			return 
		}
		if  := ();  != nil {
			return 
		}
		 = 
	}
	return .Err()
}

func planBandResources( context.Context,  *preparedNode,  image.Rectangle,  []*preparedPatternTile,  FrameOptions,  int) (renderResources, error) {
	// Patterns are rendered and retained once, not re-created for every band.
	,  := planRenderResourcesRegion(, nil, image.Rectangle{}, , .MaxOffscreenBytes, true)
	if  != nil {
		return renderResources{}, 
	}
	 := .peakOffscreenBytes - .rasterizerBytes
	var  int64
	for ,  := range  {
		var  bool
		,  = checkedAdd(, .tileBytes)
		if ! {
			return renderResources{}, fmt.Errorf("d2raster: pattern tile storage exceeds the int64 domain")
		}
	}
	for  := .Min.Y;  < .Max.Y; {
		if  := .Err();  != nil {
			return renderResources{}, 
		}
		 :=  + min(, .Max.Y-)
		 := image.Rect(.Min.X, , .Max.X, )
		,  := planRenderResourcesRegion(, , , nil, .MaxOffscreenBytes, true)
		if  != nil {
			return renderResources{}, 
		}
		,  := checkedAdd(, .peakOffscreenBytes-.rasterizerBytes)
		if ! {
			return renderResources{}, fmt.Errorf("d2raster: peak offscreen pixel storage exceeds the int64 domain")
		}
		 = maxInt64(, )
		mergeRasterizerRequirements(&, )
		if  := addScanlineWork(&, .scanlineWork);  != nil {
			return renderResources{}, 
		}
		if  := addResourceWork(&, .evenOddClipWork);  != nil {
			return renderResources{}, 
		}
		.peakOffscreenBytes,  = checkedAdd(, .rasterizerBytes)
		if ! {
			return renderResources{}, fmt.Errorf("d2raster: peak offscreen pixel storage exceeds the int64 domain")
		}
		// Reject as soon as monotonic totals exceed their limits rather than
		// continuing to plan a potentially very tall rejected image.
		if .peakOffscreenBytes > .MaxOffscreenBytes {
			return renderResources{}, fmt.Errorf("d2raster: peak offscreen pixel storage %d bytes exceeds limit %d", .peakOffscreenBytes, .MaxOffscreenBytes)
		}
		if .evenOddClipWork > .MaxEvenOddClipWork {
			return renderResources{}, fmt.Errorf("d2raster: even-odd clip work %d exceeds limit %d", .evenOddClipWork, .MaxEvenOddClipWork)
		}
		if .scanlineWork > .MaxScanlineWork {
			return renderResources{}, fmt.Errorf("d2raster: scanline work %d exceeds limit %d", .scanlineWork, .MaxScanlineWork)
		}
		 = 
	}
	return , nil
}

// setBandReferenceBounds records the canvas origins used by an ordinary full
// render. Scanline inputs are rounded to float32 relative to those same origins
// before translating into each band, avoiding changes to antialiased pixels.
func setBandReferenceBounds( context.Context,  *preparedNode,  image.Rectangle) error {
	if  := .Err();  != nil {
		return 
	}
	if  == nil || .opacity == 0 {
		return nil
	}
	 := .bounds.Intersect()
	if .Empty() {
		return nil
	}
	 := 
	if len(.filters) != 0 {
		 = .contentBounds
	} else if .isolated || .opacity < 1 || .blend != d2scene.BlendNormal || .clip != nil || .mask != nil {
		 = 
	}
	if .primitive != nil {
		.primitive.referenceBounds = 
		if .primitive.stroke != nil {
			.primitive.stroke.referenceBounds = 
		}
		if .primitive.vector != nil {
			if  := (, .primitive.vector, );  != nil {
				return 
			}
		}
	}
	for ,  := range .children {
		if  := (, , );  != nil {
			return 
		}
	}
	if .clip != nil {
		.clip.referenceBounds = 
	}
	if .mask != nil {
		return (, .mask.root, )
	}
	return nil
}

func setRasterizerReference( *scanline.Rasterizer, ,  image.Point,  bool) image.Point {
	if ! {
		return 
	}
	.SetOriginOffset(float64(.X)-float64(.X), float64(.Y)-float64(.Y))
	return 
}