{ Apophysis Copyright (C) 2001-2004 Mark Townsend This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. } unit Gradient; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, ComCtrls, Registry, cmap, Menus, ToolWin, Buttons, AppEvnts; const PixelCountMax = 32768; type pRGBTripleArray = ^TRGBTripleArray; TRGBTripleArray = array[0..PixelCountMax - 1] of TRGBTriple; type TGradientForm = class(TForm) pnlPalette: TPanel; pnlControls: TPanel; cmbPalette: TComboBox; GradientImage: TImage; PopupMenu: TPopupMenu; mnuReverse: TMenuItem; mnuInvert: TMenuItem; btnMenu: TSpeedButton; Popup: TPopupMenu; mnuHue: TMenuItem; mnuRotate: TMenuItem; N1: TMenuItem; mnuSaturation: TMenuItem; mnuBrightness: TMenuItem; N2: TMenuItem; ScrollBar: TScrollBar; lblVal: TLabel; mnuBlur: TMenuItem; btnOpen: TSpeedButton; N3: TMenuItem; mnuGradientBrowser: TMenuItem; mnuSmoothPalette: TMenuItem; btnSmoothPalette: TSpeedButton; N4: TMenuItem; SaveGradient1: TMenuItem; SaveasMapfile1: TMenuItem; SaveDialog: TSaveDialog; Label1: TLabel; btnPaste: TSpeedButton; btnCopy: TSpeedButton; N5: TMenuItem; mnuCopy: TMenuItem; mnuPaste: TMenuItem; ApplicationEvents: TApplicationEvents; mnuSaveasDefault: TMenuItem; N6: TMenuItem; mnuRandomize: TMenuItem; N7: TMenuItem; mnuFrequency: TMenuItem; Contrast1: TMenuItem; procedure cmbPaletteChange(Sender: TObject); procedure FormShow(Sender: TObject); procedure btnApplyClick(Sender: TObject); procedure DrawPalette; procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure mnuReverseClick(Sender: TObject); procedure mnuInvertClick(Sender: TObject); procedure btnMenuClick(Sender: TObject); procedure mnuRotateClick(Sender: TObject); procedure mnuHueClick(Sender: TObject); procedure mnuSaturationClick(Sender: TObject); procedure ScrollBarChange(Sender: TObject); procedure mnuBrightnessClick(Sender: TObject); procedure mnuBlurClick(Sender: TObject); procedure btnOpenClick(Sender: TObject); procedure mnuSmoothPaletteClick(Sender: TObject); procedure SaveGradient1Click(Sender: TObject); procedure SaveasMapfile1Click(Sender: TObject); procedure cmbPaletteDrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); procedure ScrollBarScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer); procedure btnCopyClick(Sender: TObject); procedure btnPasteClick(Sender: TObject); procedure ApplicationEventsActivate(Sender: TObject); procedure mnuSaveasDefaultClick(Sender: TObject); procedure mnuRandomizeClick(Sender: TObject); procedure mnuFrequencyClick(Sender: TObject); procedure Contrast1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private procedure Apply; function Blur(const radius: integer; const pal: TColorMap): TColorMap; function Frequency(const times: Integer; const pal: TColorMap): TColorMap; procedure SaveMap(FileName: string); public Palette: TColorMap; BackupPal: TColorMap; procedure UpdateGradient(Pal: TColorMap); function RandomGradient: TColorMap; end; var GradientForm: TGradientForm; pCmap: integer; function GradientInClipboard: boolean; procedure RGBToHSV(R, G, B: byte; var H, S, V: real); procedure HSVToRGB(H, S, V: real; var Rb, Gb, Bb: integer); implementation uses RndFlame, Main, cmapdata, Math, Browser, Editor, Global, Save, Adjust, Mutate, ClipBrd, GradientHlpr; {$R *.DFM} procedure TGradientForm.Apply; begin MainForm.StopThread; MainForm.UpdateUndo; MainCp.CmapIndex := cmbPalette.ItemIndex; MainCp.cmap := Palette; if EditForm.visible then EditForm.UpdateDisplay; // if AdjustForm.visible then AdjustForm.UpdateDisplay; if MutateForm.Visible then MutateForm.UpdateDisplay; MainForm.RedrawTimer.enabled := true; end; procedure TGradientForm.SaveMap(FileName: string); var i: Integer; l: string; MapFile: TextFile; begin { Save a map file } AssignFile(MapFile, FileName); try ReWrite(MapFile); { first line with comment } l := Format(' %3d %3d %3d Exported from Apophysis 2.0', [Palette[0][0], palette[0][1], palette[0][2]]); Writeln(MapFile, l); { now the rest } for i := 1 to 255 do begin l := Format(' %3d %3d %3d', [Palette[i][0], palette[i][1], palette[i][2]]); Writeln(MapFile, l); end; CloseFile(MapFile); except on EInOutError do Application.MessageBox(PChar('Cannot Open File: ' + FileName), 'Apophysis', 16); end; end; procedure TGradientForm.UpdateGradient(Pal: TColorMap); begin Palette := Pal; BackupPal := Pal; DrawPalette; ScrollBar.Position := 0; end; procedure HSVToRGB(H, S, V: real; var Rb, Gb, Bb: integer); var R, G, B, Sa, Va, Hue, i, f, p, q, t: real; begin R := 0; G := 0; B := 0; Sa := S / 100; Va := V / 100; if S = 0 then begin R := Va; G := Va; B := Va; end else begin Hue := H / 60; if Hue = 6 then Hue := 0; i := Int(Hue); f := Hue - i; p := Va * (1 - Sa); q := Va * (1 - (Sa * f)); t := Va * (1 - (Sa * (1 - f))); case Round(i) of 0: begin R := Va; G := t; B := p; end; 1: begin R := q; G := Va; B := p; end; 2: begin R := p; G := Va; B := t; end; 3: begin R := p; G := q; B := Va; end; 4: begin R := t; G := p; B := Va; end; 5: begin R := Va; G := p; B := q; end; end; end; Rb := Round(Int(255.9999 * R)); Gb := Round(Int(255.9999 * G)); Bb := Round(Int(255.9999 * B)); end; procedure RGBToHSV(R, G, B: byte; var H, S, V: real); var vRed, vGreen, vBlue, Mx, Mn, Va, Sa, rc, gc, bc: real; begin vRed := R / 255; vGreen := G / 255; vBlue := B / 255; Mx := vRed; if vGreen > Mx then Mx := vGreen; if vBlue > Mx then Mx := vBlue; Mn := vRed; if vGreen < Mn then Mn := vGreen; if vBlue < Mn then Mn := vBlue; Va := Mx; if Mx <> 0 then Sa := (Mx - Mn) / Mx else Sa := 0; if Sa = 0 then H := 0 else begin rc := (Mx - vRed) / (Mx - Mn); gc := (Mx - vGreen) / (Mx - Mn); bc := (Mx - vBlue) / (Mx - Mn); if Mx = vRed then H := bc - gc else if Mx = vGreen then H := 2 + rc - bc else if Mx = vBlue then H := 4 + gc - rc; H := H * 60; if H < 0 then H := H + 360; end; S := Sa * 100; V := Va * 100; end; function TGradientForm.Blur(const Radius: Integer; const pal: TColorMap): TColorMap; var r, g, b, n, i, j, k: Integer; begin Result := Pal; if Radius <> 0 then for i := 0 to 255 do begin n := -1; r := 0; g := 0; b := 0; for j := i - radius to i + radius do begin inc(n); k := (256 + j) mod 256; if k <> i then begin r := r + Pal[k][0]; g := g + Pal[k][1]; b := b + Pal[k][2]; end; end; if n <> 0 then begin Result[i][0] := r div n; Result[i][1] := g div n; Result[i][2] := b div n; end; end; end; function TGradientForm.Frequency(const times: Integer; const pal: TColorMap): TColorMap; { This can be improved } var n, i, j: Integer; begin Result := Pal; if times <> 1 then begin n := 256 div times; for j := 0 to times do for i := 0 to n do begin if (i + j * n) < 256 then begin Result[i + j * n][0] := pal[i * times][0]; Result[i + j * n][1] := pal[i * times][1]; Result[i + j * n][2] := pal[i * times][2]; end; end; end; end; procedure TGradientForm.DrawPalette; var i, j: integer; Row: pRGBTripleArray; BitMap: TBitMap; begin BitMap := TBitMap.Create; try Bitmap.PixelFormat := pf24bit; BitMap.Width := 256; BitMap.Height := 1; for j := 0 to Bitmap.Height - 1 do begin Row := Bitmap.Scanline[j]; for i := 0 to Bitmap.Width - 1 do begin with Row[i] do begin rgbtRed := Palette[i][0]; rgbtGreen := Palette[i][1]; rgbtBlue := Palette[i][2]; end end end; GradientImage.Picture.Graphic := Bitmap; GradientImage.Refresh; finally BitMap.Free; end; end; procedure TGradientForm.cmbPaletteChange(Sender: TObject); var i: integer; begin i := cmbPalette.ItemIndex; GetCmap(i, 1, Palette); BackupPal := Palette; ScrollBar.Position := 0; DrawPalette; Apply; end; procedure TGradientForm.FormShow(Sender: TObject); var Registry: TRegistry; begin { Read posution from registry } Registry := TRegistry.Create; try Registry.RootKey := HKEY_CURRENT_USER; if Registry.OpenKey('Software\' + APP_NAME + '\Forms\Gradient', False) then begin if Registry.ValueExists('Left') then GradientForm.Left := Registry.ReadInteger('Left'); if Registry.ValueExists('Top') then GradientForm.Top := Registry.ReadInteger('Top'); end; Registry.CloseKey; finally Registry.Free; end; DrawPalette; end; procedure TGradientForm.FormClose(Sender: TObject; var Action: TCloseAction); var Registry: TRegistry; begin { Write position to registry } Registry := TRegistry.Create; try Registry.RootKey := HKEY_CURRENT_USER; { Defaults } if Registry.OpenKey('\Software\' + APP_NAME + '\Forms\Gradient', True) then begin Registry.WriteInteger('Top', GradientForm.Top); Registry.WriteInteger('Left', GradientForm.Left); end; finally Registry.Free; end; end; procedure TGradientForm.btnApplyClick(Sender: TObject); begin Apply; end; procedure TGradientForm.mnuReverseClick(Sender: TObject); var i: integer; pal: TColorMap; begin for i := 0 to 255 do begin pal[i][0] := Palette[255 - i][0]; pal[i][1] := Palette[255 - i][1]; pal[i][2] := Palette[255 - i][2]; end; UpdateGradient(pal); Apply; end; procedure TGradientForm.mnuInvertClick(Sender: TObject); var i: integer; begin for i := 0 to 255 do begin Palette[i][0] := 255 - Palette[i][0]; Palette[i][1] := 255 - Palette[i][1]; Palette[i][2] := 255 - Palette[i][2]; end; UpdateGradient(palette); Apply; end; procedure TGradientForm.btnMenuClick(Sender: TObject); begin Popup.Popup(btnMenu.ClientOrigin.x, btnMenu.ClientOrigin.y + btnMenu.Height); end; procedure TGradientForm.ScrollBarChange(Sender: TObject); var intens, i, r, g, b: integer; h, s, v: real; begin lblVal.Caption := IntToStr(ScrollBar.Position); if btnMenu.Caption = 'Hue' then begin for i := 0 to 255 do begin RGBToHSV(BackupPal[i][0], BackupPal[i][1], BackupPal[i][2], h, s, v); h := Round(360 + h + ScrollBar.Position) mod 360; HSVToRGB(h, s, v, Palette[i][0], Palette[i][1], Palette[i][2]); end; end; if btnMenu.Caption = 'Saturation' then begin for i := 0 to 255 do begin RGBToHSV(BackupPal[i][0], BackupPal[i][1], BackupPal[i][2], h, s, v); s := s + ScrollBar.Position; if s > 100 then s := 100; if s < 0 then s := 0; HSVToRGB(h, s, v, Palette[i][0], Palette[i][1], Palette[i][2]); end; end; if btnMenu.Caption = 'Contrast' then begin intens := scrollBar.Position; if intens > 0 then intens := intens * 2; for i := 0 to 255 do begin r := BackupPal[i][0]; g := BackupPal[i][1]; b := BackupPal[i][2]; r := round(r + intens / 100 * (r - 127)); g := round(g + intens / 100 * (g - 127)); b := round(b + intens / 100 * (b - 127)); if R > 255 then R := 255 else if R < 0 then R := 0; if G > 255 then G := 255 else if G < 0 then G := 0; if B > 255 then B := 255 else if B < 0 then B := 0; Palette[i][0] := r; Palette[i][1] := g; Palette[i][2] := b; end; end; if btnMenu.Caption = 'Brightness' then begin for i := 0 to 255 do begin Palette[i][0] := BackupPal[i][0] + ScrollBar.Position; if Palette[i][0] > 255 then Palette[i][0] := 255; if Palette[i][0] < 0 then Palette[i][0] := 0; Palette[i][1] := BackupPal[i][1] + ScrollBar.Position; if Palette[i][1] > 255 then Palette[i][1] := 255; if Palette[i][1] < 0 then Palette[i][1] := 0; Palette[i][2] := BackupPal[i][2] + ScrollBar.Position; if Palette[i][2] > 255 then Palette[i][2] := 255; if Palette[i][2] < 0 then Palette[i][2] := 0; end; end; if btnMenu.Caption = 'Rotate' then begin for i := 0 to 255 do begin Palette[i][0] := BackupPal[(255 + i - ScrollBar.Position) mod 256][0]; Palette[i][1] := BackupPal[(255 + i - ScrollBar.Position) mod 256][1]; Palette[i][2] := BackupPal[(255 + i - ScrollBar.Position) mod 256][2]; end; end; if btnMenu.Caption = 'Blur' then begin Palette := Blur(ScrollBar.Position, BackupPal); end; if btnMenu.Caption = 'Frequency' then begin Palette := Frequency(ScrollBar.Position, BackupPal); end; DrawPalette; end; { ***************************** Adjust menu ********************************* } procedure TGradientForm.mnuRotateClick(Sender: TObject); begin btnMenu.Caption := 'Rotate'; BackupPal := Palette; ScrollBar.Min := 0; ScrollBar.Max := 255; ScrollBar.LargeChange := 15; ScrollBar.Position := 0; end; procedure TGradientForm.mnuHueClick(Sender: TObject); begin btnMenu.Caption := 'Hue'; BackupPal := Palette; ScrollBar.Min := 0; ScrollBar.Max := 360; ScrollBar.LargeChange := 15; ScrollBar.Position := 0; end; procedure TGradientForm.mnuBrightnessClick(Sender: TObject); begin btnMenu.Caption := 'Brightness'; BackupPal := Palette; ScrollBar.Min := -255; ScrollBar.Max := 255; ScrollBar.LargeChange := 15; ScrollBar.Position := 0; end; procedure TGradientForm.mnuSaturationClick(Sender: TObject); begin btnMenu.Caption := 'Saturation'; BackupPal := Palette; ScrollBar.Min := -100; ScrollBar.Max := 100; ScrollBar.LargeChange := 15; ScrollBar.Position := 0; end; procedure TGradientForm.mnuBlurClick(Sender: TObject); begin btnMenu.Caption := 'Blur'; BackupPal := Palette; ScrollBar.Min := 0; ScrollBar.Max := 127; ScrollBar.LargeChange := 15; ScrollBar.Position := 0; end; procedure TGradientForm.mnuFrequencyClick(Sender: TObject); begin btnMenu.Caption := 'Frequency'; BackupPal := Palette; ScrollBar.Min := 1; ScrollBar.Max := 10; ScrollBar.LargeChange := 1; ScrollBar.Position := 1; end; procedure TGradientForm.btnOpenClick(Sender: TObject); begin GradientBrowser.Filename := GradientFile; GradientBrowser.Show; end; procedure TGradientForm.mnuSmoothPaletteClick(Sender: TObject); begin MainForm.SmoothPalette; end; procedure TGradientForm.SaveGradient1Click(Sender: TObject); var gradstr: TStringList; begin gradstr := TStringList.Create; try SaveForm.Caption := 'Save Gradient'; SaveForm.Filename := GradientFile; SaveForm.Title := MainCp.name; if SaveForm.ShowModal = mrOK then begin gradstr.add(CleanIdentifier(SaveForm.Title) + ' {'); gradstr.add(MainForm.GradientFromPalette(Palette, SaveForm.Title)); gradstr.add('}'); if MainForm.SaveGradient(gradstr.text, SaveForm.Title, SaveForm.Filename) then GradientFile := SaveForm.FileName; end; finally gradstr.free end; end; procedure TGradientForm.SaveasMapfile1Click(Sender: TObject); begin SaveDialog.Filename := MainCp.name + '.map'; if SaveDialog.execute then SaveMap(SaveDialog.Filename); end; procedure TGradientForm.cmbPaletteDrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); var i, j: integer; Row: pRGBTripleArray; Bitmap: TBitmap; pal: TColorMap; PalName: string; begin { Draw the preset palettes on the combo box items } GetCMap(index, 1, pal); GetCmapName(index, PalName); BitMap := TBitMap.create; Bitmap.PixelFormat := pf24bit; BitMap.Width := 256; BitMap.Height := 100; for j := 0 to Bitmap.Height - 1 do begin Row := Bitmap.Scanline[j]; for i := 0 to Bitmap.Width - 1 do begin with Row[i] do begin rgbtRed := Pal[i][0]; rgbtGreen := Pal[i][1]; rgbtBlue := Pal[i][2]; end end end; with Control as TComboBox do begin Canvas.Rectangle(Rect); Canvas.TextOut(4, Rect.Top, PalName); Rect.Left := (Rect.Left + rect.Right) div 2; Canvas.StretchDraw(Rect, Bitmap); end; BitMap.Free; end; procedure TGradientForm.ScrollBarScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer); begin if ScrollCode = scEndScroll then Apply; end; procedure TGradientForm.btnCopyClick(Sender: TObject); var gradstr: TStringList; begin gradstr := TStringList.Create; try gradstr.add(CleanIdentifier(MainCp.name) + ' {'); gradstr.add('gradient:'); gradstr.add(' title="' + MainCp.name + '" smooth=no'); gradstr.add(GradientString(Palette)); gradstr.add('}'); Clipboard.SetTextBuf(PChar(gradstr.text)); btnPaste.enabled := true; mnuPaste.enabled := true; // MainForm.btnPaste.enabled := False; MainForm.mnuPaste.enabled := False; finally gradstr.free end; end; procedure TGradientForm.btnPasteClick(Sender: TObject); begin if Clipboard.HasFormat(CF_TEXT) then begin UpdateGradient(CreatePalette(Clipboard.AsText)); Apply; end; end; function GradientInClipboard: boolean; var gradstr: TStringList; begin { returns true if gradient in clipboard - can be tricked } result := true; if Clipboard.HasFormat(CF_TEXT) then begin gradstr := TStringList.Create; try gradstr.text := Clipboard.AsText; if (Pos('}', gradstr.text) = 0) or (Pos('{', gradstr.text) = 0) or (Pos('gradient:', gradstr.text) = 0) or (Pos('fractal:', gradstr.text) <> 0) then begin result := false; exit; end; finally gradstr.free; end; end else result := false; end; procedure TGradientForm.ApplicationEventsActivate(Sender: TObject); begin if GradientInClipboard then begin mnuPaste.enabled := true; btnPaste.enabled := true; end else begin mnuPaste.enabled := false; btnPaste.enabled := false; end; end; procedure TGradientForm.mnuSaveasDefaultClick(Sender: TObject); begin DefaultPalette := Palette; SaveMap(AppPath + 'default.map'); end; function TGradientForm.RandomGradient: TColorMap; begin Result := GradientHelper.RandomGradient; end; procedure TGradientForm.mnuRandomizeClick(Sender: TObject); begin GradientForm.UpdateGradient(RandomGradient); GradientForm.Apply; end; procedure TGradientForm.Contrast1Click(Sender: TObject); begin btnMenu.Caption := 'Contrast'; BackupPal := Palette; ScrollBar.Min := -100; ScrollBar.Max := 100; ScrollBar.LargeChange := 15; ScrollBar.Position := 0; end; procedure TGradientForm.FormCreate(Sender: TObject); begin Sendmessage(cmbPalette.Handle, CB_SETDROPPEDWIDTH , cmbPalette.width * 2, 0); end; end.