diff --git a/2.10/Source/ControlPoint.pas b/2.10/Source/ControlPoint.pas index 666e6cc..e1c68d8 100644 --- a/2.10/Source/ControlPoint.pas +++ b/2.10/Source/ControlPoint.pas @@ -32,8 +32,6 @@ const BRIGHT_ADJUST = 2.3; type - PLongintArray = ^TLongintArray; - TLongintArray = array[0..8192] of Longint; TVariation = (vLinear, vSinusoidal, vSpherical, vSwirl, vHorseshoe, vPolar, vHandkerchief, vHeart, vDisc, vSpiral, vHyperbolic, vSquare, vEx, vJulia, vBent, vWaves, vFisheye, vPopcorn, vExponential, vPower, vCosine, @@ -87,6 +85,8 @@ type FTwoColorDimensions: Boolean; private procedure PreparePropTable; + function getppux: double; + function getppuy: double; public procedure SaveToStringlist(sl: TStringlist); @@ -124,6 +124,9 @@ type procedure ZoomIn(Factor: double); procedure MoveRect(R: TRect); procedure Rotate(Angle: double); + + property ppux: double read getppux; + property ppuy: double read getppuy; end; function add_symmetry_to_control_point(var cp: TControlPoint; sym: integer): integer; @@ -239,7 +242,6 @@ var i: Integer; px, py, pc: double; dx, dy, tx, ty: double; - nx, ny: double; r: double; s, v, a: double; n0, n1, m0, m1: double; @@ -1734,6 +1736,18 @@ begin mixed_var_distrib[i] := 8 + i - High(startmixed_var_distrib) - 1; end; +/////////////////////////////////////////////////////////////////////////////// +function TControlPoint.getppux: double; +begin + result := pixels_per_unit * power(2, zoom) +end; + +/////////////////////////////////////////////////////////////////////////////// +function TControlPoint.getppuy: double; +begin + result := pixels_per_unit * power(2, zoom) +end; + /////////////////////////////////////////////////////////////////////////////// initialization FillVarDisturb diff --git a/2.10/Source/ImageMaker.pas b/2.10/Source/ImageMaker.pas new file mode 100644 index 0000000..d401074 --- /dev/null +++ b/2.10/Source/ImageMaker.pas @@ -0,0 +1,649 @@ +unit ImageMaker; + +interface + +uses + Windows, Graphics, ControlPoint, Render; + +type + TImageMaker = class + private + FOversample: Integer; + FFilterSize: Integer; + FFilter: array of array of double; + + FBitmap: TBitmap; + FAlphaBitmap: TBitmap; + FTransparentImage: TBitmap; + Fcp: Tcontrolpoint; + + FBucketWidth: Int64; + FBuckets: TBucketArray; + FOnProgress: TOnProgress; + + procedure CreateFilter; + procedure NormalizeFilter; + procedure SetOnProgress(const Value: TOnProgress); + + procedure Progress(value: double); + + function GetTransparentImage: TBitmap; + + procedure CreateImage_MB(YOffset: integer = 0); + procedure CreateImage_Flame3(YOffset: integer = 0); + + public + destructor Destroy; override; + + function GetImage: TBitmap; + + procedure SetCP(CP: TControlPoint); + procedure Init; + procedure SetBucketData(const Buckets: TBucketArray; const BucketWidth: int64); + + function GetFilterSize: Integer; + + procedure CreateImage(YOffset: integer = 0); + procedure SaveImage(const FileName: String); + + property OnProgress: TOnProgress + read FOnProgress + write SetOnProgress; + end; + +implementation + +uses + Math, SysUtils, PngImage, JPEG, Global, Types; + +{ TImageMaker } + +type + TRGB = packed Record + red: byte; + green: byte; + blue: byte; + end; + + PByteArray = ^TByteArray; + TByteArray = array[0..0] of byte; +// PLongintArray = ^TLongintArray; +// TLongintArray = array[0..0] of Longint; + PRGBArray = ^TRGBArray; + TRGBArray = array[0..0] of TRGB; + +/////////////////////////////////////////////////////////////////////////////// +destructor TImageMaker.Destroy; +begin + if assigned(FBitmap) then + FBitmap.Free; + + if assigned(FAlphaBitmap) then + FAlphaBitmap.Free; + + if assigned(FTransparentImage) then + FTransparentImage.Free; + + inherited; +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.CreateFilter; +var + i, j: integer; + fw: integer; + adjust: double; + ii, jj: double; +begin + FOversample := fcp.spatial_oversample; + fw := Trunc(2.0 * FILTER_CUTOFF * FOversample * fcp.spatial_filter_radius); + FFilterSize := fw + 1; + + // make sure it has same parity as oversample + if odd(FFilterSize + FOversample) then + inc(FFilterSize); + + if (fw > 0.0) then + adjust := (1.0 * FILTER_CUTOFF * FFilterSize) / fw + else + adjust := 1.0; + + setLength(FFilter, FFilterSize, FFilterSize); + for i := 0 to FFilterSize - 1 do begin + for j := 0 to FFilterSize - 1 do begin + ii := ((2.0 * i + 1.0)/ FFilterSize - 1.0) * adjust; + jj := ((2.0 * j + 1.0)/ FFilterSize - 1.0) * adjust; + + FFilter[i, j] := exp(-2.0 * (ii * ii + jj * jj)); + end; + end; + + Normalizefilter; +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.NormalizeFilter; +var + i, j: integer; + t: double; +begin + t := 0; + for i := 0 to FFilterSize - 1 do + for j := 0 to FFilterSize - 1 do + t := t + FFilter[i, j]; + + for i := 0 to FFilterSize - 1 do + for j := 0 to FFilterSize - 1 do + FFilter[i, j] := FFilter[i, j] / t; +end; + +/////////////////////////////////////////////////////////////////////////////// +function TImageMaker.GetFilterSize: Integer; +begin + Result := FFiltersize; +end; + +/////////////////////////////////////////////////////////////////////////////// +function TImageMaker.GetImage: TBitmap; +begin + Result := GetTransparentImage; +// Result := FBitmap; +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.Init; +begin + if not Assigned(FBitmap) then + FBitmap := TBitmap.Create; + + FBitmap.PixelFormat := pf24bit; + + FBitmap.Width := Fcp.Width; + FBitmap.Height := Fcp.Height; + + if not Assigned(FAlphaBitmap) then + FAlphaBitmap := TBitmap.Create; + + FAlphaBitmap.PixelFormat := pf8bit; + FAlphaBitmap.Width := Fcp.Width; + FAlphaBitmap.Height := Fcp.Height; + + CreateFilter; +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.SetBucketData(const Buckets: TBucketArray; const BucketWidth: int64); +begin + FBuckets := Buckets; + FBucketWidth := BucketWidth; +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.SetCP(CP: TControlPoint); +begin + Fcp := CP; +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.SetOnProgress(const Value: TOnProgress); +begin + FOnProgress := Value; +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.CreateImage(YOffset: integer); +begin + CreateImage_Flame3(YOffset); +// CreateImage_MB(YOffset); +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.CreateImage_Flame3(YOffset: integer); +var + gamma: double; + i, j: integer; + alpha: double; + ai, ri, gi, bi: Integer; + bgtot: TRGB; + ls: double; + ii, jj: integer; + fp: array[0..3] of double; + Row: PRGBArray; + AlphaRow: PbyteArray; + vib, notvib: Integer; + bgi: array[0..2] of Integer; + bucketpos: Integer; + filterValue: double; + filterpos: Integer; + lsa: array[0..1024] of double; + sample_density: double; + gutter_width: integer; + k1, k2: double; + area: double; +begin + if fcp.gamma = 0 then + gamma := fcp.gamma + else + gamma := 1 / fcp.gamma; + vib := round(fcp.vibrancy * 256.0); + notvib := 256 - vib; + + bgi[0] := round(fcp.background[0]); + bgi[1] := round(fcp.background[1]); + bgi[2] := round(fcp.background[2]); + bgtot.red := bgi[0]; + bgtot.green := bgi[1]; + bgtot.blue := bgi[2]; + + gutter_width := FBucketwidth - FOversample * fcp.Width; +// gutter_width := 2 * ((25 - Foversample) div 2); + + FBitmap.PixelFormat := pf24bit; + + sample_density := fcp.sample_density * power(2, fcp.zoom) * power(2, fcp.zoom); + k1 := (fcp.Contrast * BRIGHT_ADJUST * fcp.brightness * 268 * PREFILTER_WHITE) / 256.0; + area := FBitmap.Width * FBitmap.Height / (fcp.ppux * fcp.ppuy); + k2 := (FOversample * FOversample) / (fcp.Contrast * area * fcp.White_level * sample_density); + + lsa[0] := 0; + for i := 1 to 1024 do begin + lsa[i] := (k1 * log10(1 + fcp.White_level * i * k2)) / (fcp.White_level * i); + end; + + ls := 0; + ai := 0; + bucketpos := 0; + for i := 0 to fcp.Height - 1 do begin +// if FStop then +// Break; + + Progress(i / fcp.Height); + AlphaRow := PByteArray(FAlphaBitmap.scanline[YOffset + i]); + Row := PRGBArray(FBitmap.scanline[YOffset + i]); + for j := 0 to fcp.Width - 1 do begin + if FFilterSize > 1 then begin + fp[0] := 0; + fp[1] := 0; + fp[2] := 0; + fp[3] := 0; + + for ii := 0 to FFilterSize - 1 do begin + for jj := 0 to FFilterSize - 1 do begin + filterValue := FFilter[ii, jj]; + filterpos := bucketpos + ii * FBucketWidth + jj; + + ls := lsa[Min(1023, FBuckets[filterpos].Count)]; + + fp[0] := fp[0] + filterValue * ls * FBuckets[filterpos].Red; + fp[1] := fp[1] + filterValue * ls * FBuckets[filterpos].Green; + fp[2] := fp[2] + filterValue * ls * FBuckets[filterpos].Blue; + fp[3] := fp[3] + filterValue * ls * FBuckets[filterpos].Count; + end; + end; + + fp[0] := fp[0] / PREFILTER_WHITE; + fp[1] := fp[1] / PREFILTER_WHITE; + fp[2] := fp[2] / PREFILTER_WHITE; + fp[3] := fcp.white_level * fp[3] / PREFILTER_WHITE; + end else begin + ls := lsa[Min(1023, FBuckets[bucketpos].count)] / PREFILTER_WHITE; + + fp[0] := ls * FBuckets[bucketpos].Red; + fp[1] := ls * FBuckets[bucketpos].Green; + fp[2] := ls * FBuckets[bucketpos].Blue; + fp[3] := ls * FBuckets[bucketpos].Count * fcp.white_level; + end; + + Inc(bucketpos, FOversample); + + if (fp[3] > 0.0) then begin + alpha := power(fp[3], gamma); + ls := vib * alpha / fp[3]; + ai := round(alpha * 256); + if (ai < 0) then + ai := 0 + else if (ai > 255) then + ai := 255; + ai := 255 - ai; + end else begin + // no intensity so simply set the BG; + Row[j] := bgtot; + AlphaRow[j] := 0; + continue; + end; + + if (notvib > 0) then + ri := Round(ls * fp[0] + notvib * power(fp[0], gamma)) + else + ri := Round(ls * fp[0]); + ri := ri + (ai * bgi[0]) shr 8; + if (ri < 0) then + ri := 0 + else if (ri > 255) then + ri := 255; + + if (notvib > 0) then + gi := Round(ls * fp[1] + notvib * power(fp[1], gamma)) + else + gi := Round(ls * fp[1]); + gi := gi + (ai * bgi[1]) shr 8; + if (gi < 0) then + gi := 0 + else if (gi > 255) then + gi := 255; + + if (notvib > 0) then + bi := Round(ls * fp[2] + notvib * power(fp[2], gamma)) + else + bi := Round(ls * fp[2]); + bi := bi + (ai * bgi[2]) shr 8; + if (bi < 0) then + bi := 0 + else if (bi > 255) then + bi := 255; + + Row[j].red := ri; + Row[j].green := gi; + Row[j].blue := bi; + + AlphaRow[j] := 255 - ai; + end; + + Inc(bucketpos, gutter_width); + Inc(bucketpos, (FOversample - 1) * FBucketWidth); + end; + + FBitmap.PixelFormat := pf24bit; + + Progress(1); +end; + +/////////////////////////////////////////////////////////////////////////////// +// michael baranov transparancy code forrm flamesong used +procedure TImageMaker.CreateImage_MB(YOffset: integer); +var + gamma: double; + i, j: integer; + alpha: double; + ai, ri, gi, bi: Integer; + bgtot: TRGB; + ls: double; + ii, jj: integer; + fp: array[0..3] of double; + Row: PRGBArray; + AlphaRow: PbyteArray; + vib, notvib: Integer; + bgi: array[0..2] of Integer; + bucketpos: Integer; + filterValue: double; + filterpos: Integer; + lsa: array[0..1024] of double; + sample_density: double; + gutter_width: integer; + k1, k2: double; + area: double; + MaxA: int64; + ACount: double; + offsetLow: double; + offsetHigh: double; + densLow: double; + densHigh: double; + divisor: double; +begin + if fcp.gamma = 0 then + gamma := fcp.gamma + else + gamma := 1 / fcp.gamma; + vib := round(fcp.vibrancy * 256.0); + notvib := 256 - vib; + + bgi[0] := round(fcp.background[0]); + bgi[1] := round(fcp.background[1]); + bgi[2] := round(fcp.background[2]); + bgtot.red := bgi[0]; + bgtot.green := bgi[1]; + bgtot.blue := bgi[2]; + + gutter_width := FBucketwidth - FOversample * fcp.Width; + + sample_density := fcp.sample_density * power(2, fcp.zoom) * power(2, fcp.zoom); + k1 := (fcp.Contrast * BRIGHT_ADJUST * fcp.brightness * 268 * PREFILTER_WHITE) / 256.0; + area := FBitmap.Width * FBitmap.Height / (fcp.ppux * fcp.ppuy); + k2 := (FOversample * FOversample) / (fcp.Contrast * area * fcp.White_level * sample_density); + + lsa[0] := 0; + for i := 1 to 1024 do begin + lsa[i] := (k1 * log10(1 + fcp.White_level * i * k2)) / (fcp.White_level * i); + end; + + MaxA := 0; + bucketpos := 0; + for i := 0 to fcp.Height - 1 do begin + for j := 0 to fcp.Width - 1 do begin + MaxA := Max(MaxA, FBuckets[bucketpos].Count); + Inc(bucketpos, FOversample); + end; + Inc(bucketpos, gutter_width); + Inc(bucketpos, (FOversample - 1) * FBucketWidth); + end; + + offsetLow := 0; + offsetHigh := 0.02; + densLow := MaxA * offsetLow; + densHigh := MaxA * offsetHigh; + divisor := power(MaxA * (1 - offsethigh), Gamma); + + ls := 0; + ai := 0; + bucketpos := 0; + for i := 0 to fcp.Height - 1 do begin +// if FStop then +// Break; + + Progress(i / fcp.Height); + AlphaRow := PByteArray(FAlphaBitmap.scanline[YOffset + i]); + Row := PRGBArray(FBitmap.scanline[YOffset + i]); + for j := 0 to fcp.Width - 1 do begin + if FFilterSize > 1 then begin + fp[0] := 0; + fp[1] := 0; + fp[2] := 0; + fp[3] := 0; + ACount := 0; + + for ii := 0 to FFilterSize - 1 do begin + for jj := 0 to FFilterSize - 1 do begin + filterValue := FFilter[ii, jj]; + filterpos := bucketpos + ii * FBucketWidth + jj; + + ls := lsa[Min(1023, FBuckets[filterpos].Count)]; + + fp[0] := fp[0] + filterValue * ls * FBuckets[filterpos].Red; + fp[1] := fp[1] + filterValue * ls * FBuckets[filterpos].Green; + fp[2] := fp[2] + filterValue * ls * FBuckets[filterpos].Blue; + fp[3] := fp[3] + filterValue * ls * FBuckets[filterpos].Count; + ACount := ACount + filterValue * FBuckets[filterpos].Count; + end; + end; + + fp[0] := fp[0] / PREFILTER_WHITE; + fp[1] := fp[1] / PREFILTER_WHITE; + fp[2] := fp[2] / PREFILTER_WHITE; + fp[3] := fcp.white_level * fp[3] / PREFILTER_WHITE; + end else begin + ls := lsa[Min(1023, FBuckets[bucketpos].count)] / PREFILTER_WHITE; + + fp[0] := ls * FBuckets[bucketpos].Red; + fp[1] := ls * FBuckets[bucketpos].Green; + fp[2] := ls * FBuckets[bucketpos].Blue; + fp[3] := ls * FBuckets[bucketpos].Count * fcp.white_level; + ACount := FBuckets[bucketpos].Count; + + end; + + Inc(bucketpos, FOversample); + + if (fp[3] > 0.0) then begin + if(divisor > 1E-12) then + alpha := power(ACount - densLow, Gamma) / divisor + else + alpha := 1; + +// ls := vib * alpha; + ls := vib * power(fp[3], gamma) / fp[3]; + ai := round(alpha * 256); + if (ai < 0) then + ai := 0 + else if (ai > 255) then + ai := 255; + ai := 255 - ai; + end else begin + // no intensity so simply set the BG; + Row[j] := bgtot; + AlphaRow[j] := 0; + continue; + end; + + if (notvib > 0) then + ri := Round(ls * fp[0] + notvib * power(fp[0], gamma)) + else + ri := Round(ls * fp[0]); + ri := ri + (ai * bgi[0]) shr 8; + if (ri < 0) then + ri := 0 + else if (ri > 255) then + ri := 255; + + if (notvib > 0) then + gi := Round(ls * fp[1] + notvib * power(fp[1], gamma)) + else + gi := Round(ls * fp[1]); + gi := gi + (ai * bgi[1]) shr 8; + if (gi < 0) then + gi := 0 + else if (gi > 255) then + gi := 255; + + if (notvib > 0) then + bi := Round(ls * fp[2] + notvib * power(fp[2], gamma)) + else + bi := Round(ls * fp[2]); + bi := bi + (ai * bgi[2]) shr 8; + if (bi < 0) then + bi := 0 + else if (bi > 255) then + bi := 255; + + Row[j].red := ri; + Row[j].green := gi; + Row[j].blue := bi; + + AlphaRow[j] := 255 - ai; + end; + + Inc(bucketpos, gutter_width); + Inc(bucketpos, (FOversample - 1) * FBucketWidth); + end; + + FBitmap.PixelFormat := pf24bit; + + Progress(1); +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.SaveImage(const FileName: String); +var + i,row: integer; + PngObject: TPngObject; + rowbm, rowpng: PByteArray; + JPEGImage: TJPEGImage; +begin + if UpperCase(ExtractFileExt(FileName)) = '.PNG' then begin + PngObject := TPngObject.Create; + PngObject.Assign(FBitmap); + PngObject.CreateAlpha; + for i:= 0 to FAlphaBitmap.Height - 1 do begin + rowbm := PByteArray(FAlphaBitmap.scanline[i]); + rowpng := PByteArray(PngObject.AlphaScanline[i]); + for row := 0 to FAlphaBitmap.Width -1 do begin + rowpng[row] := rowbm[row]; + end; + end; + + PngObject.SaveToFile(FileName); + PngObject.Free; + end else if UpperCase(ExtractFileExt(FileName)) = '.JPG' then begin + JPEGImage := TJPEGImage.Create; + JPEGImage.Assign(FBitmap); + JPEGImage.CompressionQuality := JPEGQuality; + JPEGImage.SaveToFile(FileName); + JPEGImage.Free; + +// with TLinearBitmap.Create do +// try +// Assign(Renderer.GetImage); +// JPEGLoader.Default.Quality := JPEGQuality; +// SaveToFile(RenderForm.FileName); +// finally +// Free; +// end; + end else begin // bitmap + FBitmap.SaveToFile(FileName); + end; + +end; + +/////////////////////////////////////////////////////////////////////////////// +procedure TImageMaker.Progress(value: double); +begin + if assigned(FOnprogress) then + FOnprogress(Value); +end; + +/////////////////////////////////////////////////////////////////////////////// +function TImageMaker.GetTransparentImage: TBitmap; +var + x,y: integer; + i,row: integer; + PngObject: TPngObject; + rowbm, rowpng: PByteArray; +begin + if assigned(FTransparentImage) then + FTransparentImage.Free; + + FTransparentImage := tBitmap.Create; +// FTransparentImage.PixelFormat := pf24bit; + + FTransparentImage.Width := Fcp.Width; + FTransparentImage.Height := Fcp.Height; + + FTransparentImage.Canvas.Brush.Color := ClSilver; + FTransparentImage.Canvas.FillRect(Rect(0,0,Fcp.Width, Fcp.Height)); + + FTransparentImage.Canvas.Brush.Color := ClWhite; + for x := 0 to ((Fcp.Width - 1) div 20) do begin + for y := 0 to ((Fcp.Height - 1) div 20) do begin + if odd(x + y) then + FTransparentImage.Canvas.FillRect(Rect(x * 20, y * 20, x * 20 + 20, y * 20 + 20)); + end; + end; + + PngObject := TPngObject.Create; + PngObject.Assign(FBitmap); + PngObject.CreateAlpha; + for i:= 0 to FAlphaBitmap.Height - 1 do begin + rowbm := PByteArray(FAlphaBitmap.scanline[i]); + rowpng := PByteArray(PngObject.AlphaScanline[i]); + for row := 0 to FAlphaBitmap.Width -1 do begin + rowpng[row] := rowbm[row]; + end; + end; + + PngObject.Draw(FTransparentImage.Canvas, Rect(0,0,Fcp.Width, Fcp.Height)); + PngObject.Free; + + Result := FTransparentImage; +end; + +/////////////////////////////////////////////////////////////////////////////// +end.