【问题标题】:Fading values general function/procedure衰落值一般功能/程序
【发布时间】:2014-11-24 21:27:08
【问题描述】:

我正在使用一个通用函数/程序,它会根据提供的数据计算我的淡入淡出时间和值,如下所示:

我将字节值保存在字节数组中:这些是起始值。然后我在其他数组中有一些记忆值:这些是新值。然后我有时间被提供,这是需要一个起始值来获得新值的时间。

每次更改值时,我都需要更新它们(精确到 0.1 秒)。我知道如果值 A 更改为 10 并且值 B 同时更改为 100,假设 1 秒,我将得到值 A em> 更新 10 次,而值 B 将更新 100 次。 到目前为止,我一直在计划使用一个计时器,间隔时间假设为 50 毫秒,它会根据变化的值和所需的时间不断计算差异,例如:change step := (Difference between start and new value / {divided by} (fade time / timer interval) )

但是考虑到值的变化是不同的,淡化时间也是不同的,而且我可以在第一次淡化结束之前执行另一个值淡化,这让我感到困惑和困难。

所以,我需要一个选项,比如说,给定索引 1、2、3、4、5、6 和 7 的值在 30 秒内更改为新值,然后在某个时间点在这两者之间,我可以订购索引 11、13 和 17 处的值,以便在 9 秒内更改为它们的新值,等等......

此外,如果值 A 将逐渐向值 B 淡化,并且另一个从 A 淡化到 C 将被订购,我希望将其添加到队列列表中,以便在第一次淡入淡出完成后立即执行。那时,第一个命令中的 B 值将成为第二个命令中的 A 值。这是由于以下事实: 上例中的 A 应始终在淡入淡出开始的那一刻被读取。这样,无论在淡入淡出之前或在淡入淡出命令和淡入淡出执行之间做了什么,它都是一个起始值。因此,我可以将 Fade1 设置为 Current -> B @ 10s 并为 Current -> C @ 10s 排队 Fade2,而 Current 在第二种情况实际上是保存为 B 的值,假设 Fade1 中的 Current 与保存为 C 的值相同。这样,该值将处于环回状态,每 10 秒更改一次。所以基本上,添加淡入淡出的命令应该只有像 SetNewFade: Dest:=B; 这样的东西。时间:=10;.

所以我可以添加 ->B@10s, ->C@10s, ->B@10s, - >C@10s,它只会从 B 循环到 C 并向后循环,直到队列列表为空。我希望我设法使这一点足够清楚,以便您理解。我真的无法更好地描述我需要实现的目标。

此外,由于所有淡入淡出都将在列表框中提供,我希望能够根据需要删除队列中的淡入淡出。但是,如果当前正在运行的淡入淡出被删除,则该值应该跳转到一个新值,就好像淡入淡出已经完成一样,然后通常在队列列表中启动新的淡入淡出,如果有的话。

如何创建最容易?使用具有固定间隔的计时器是个好主意吗?如果有很多值等待淡出,它会导致任何延迟吗?对值和时间使用动态数组(并在 StartFade 事件中填充它们并在淡入淡出完成后释放它们)是在黑暗中拍摄还是一个好的猜测?

这里有一个例子,我希望它更清楚:

A: array [0..20] of Byte;
B: array [0..20] of Byte;
C: array [0..20] of Byte;
Current: array [0..20] of Byte;

Button1 将 A 值应用到 Current 值,Button2 应用 B 值,而 Button3 应用 C em> 值,等等……

所以我在编辑框中设置了时间,比如说 5 秒,然后单击 Button1。有了这个,我添加了从 Current 到数组 A 中的值的淡入淡出,时间为 5 秒。由于它是队列中的第一个,因此它立即开始执行。在淡入淡出实际完成之前,我将时间设置为 20 秒并按下 Button2。所以我只是在队列列表中添加了另一个淡入淡出:从 Current 到数组 B 中的值。由于我正在更改相同的值(索引 0..20),因此在第一次淡入淡出完成之后 开始执行此操作。注意:淡入淡出过程不断更新 Current 数组,直到它具有与淡入淡出命令的数组相同的值!因此,第二次渐变将再次从 Current 渐变到 B,而 Current 实际上与 A 相同。

现在事情变得更加复杂的是,当我实际上只将数组中索引为 0、1、2、3 和 4 的值设置为要淡出@5sec 到 A,然后我应用索引为 5、6、7、8 和 9 的值在 @10sec 时淡化为 B 值:在这种情况下,由于我淡化的索引是不同的,所以两个淡化命令都应该执行 right离开

如果两个渐变中都有一个值(例如,如果我将索引为 4 的值添加到第二个渐变中),则只需将这个值添加到队列列表中。所以另一个立即淡出,而在第一次淡出中已经淡出的那个,等待它完成,然后按照第二个命令开始淡出。

一些附加信息:

  • 数组的长度目前不固定,但如果这很重要,可以设置为固定的。它肯定是 512 的乘数,最大为 2047。

  • 数组的数量未知,将在运行时根据需要进行修改。它们可能会被存储为记录,(例如StoredVals: array of record;Index: array of Integer(值的索引;这是为了说明哪些值存储在此记录中)和Value: array of Byte;(这些是褪色的实际值,以Current[StoredVals[0].Index[0]]为例,Current保存所有值的数据,同时保存ABC 等...仅保留在该记录中索引的值)。

  • 根据上面的描述,数组的长度并不总是相等的,因为它们并不总是改变相同数量的值。

  • 数组在初始化时从数据库中填充。由于它们可以在运行时创建,它们由 Current 值填充并存储为新数组。但这也总是写在数据库中。它们是一种记忆值,所以我可以用按钮调用它们。就此而言,我希望可以选择立即调用这些值(就像我现在所做的那样)或通过淡入淡出选项。现在,为了避免队列中的值出现问题,我正在考虑通过淡入淡出过程发送 立即 更改,仅时间为 0 秒。这样,不在队列中的值将立即应用,而如果某个值当前正在淡入淡出,它将在淡入淡出完成后应用。也就是说,这个淡入淡出过程将一直在命令流中。

如果需要任何其他额外说明,请随时询问!

我知道这真的很复杂,这就是我寻求您帮助的原因。任何部分帮助或建议将不胜感激。

【问题讨论】:

  • 并发淡入淡出会发生什么,例如S1 a>b 30 秒和 5 秒后 S2 a>c 10 秒,我希望 S1 必须终止?
  • 参见math for slow image zoom,它提供了基础知识。根据您的具体情况重写。
  • @NGLN 谢谢你的提示。会看看...

标签: delphi timer delphi-xe3


【解决方案1】:

我正在执行一般功能/程序...

实际上,您似乎在追求一个完整的程序。您正在考虑将其作为一个整体来解决,这很模糊,这就是您有这么多问题的原因。你需要学会把这个任务分解成更小的部分,并更清楚地总结需求。当前形式的问题接近于题外话,它可能更适合SE Programmers。但由于这正好适合我的小巷,我想带你过去。

要求

  • 有一组长度为 N 的值 X。
  • 可以为该集合中的一个或多个值分配一个新值。
  • 从旧值到新值的修改应在特定持续时间内分步执行。
  • 这会在过渡期间产生中间值。
  • 此转换是特定于值/索引的,即 X[0] 的持续时间可能与 X[1] 的持续时间不同。
  • 必须先完成转换,然后才能分配另一个新值。
  • 在进行转换时,可能会请求分配新值。
  • 由此得出结论,新请求应存储在队列中,以便在转换完成时,可以从队列中拉出新值请求,从而产生新的转换。

我很确定这是对您问题的正确总结。

过渡

您建议使用计时器在每个间隔上执行总转换的片段是合理的。现在,有两种方法可以计算这些

  1. 将总转换划分为固定数量的小转换,将计时器的时间间隔设置为总持续时间除以该数字,并在每个时间间隔处理所有已处理的较小转换的总和。这是您在计算更改步骤时提出的建议。这种方法的缺点是双重的:
    • Timer 的时间间隔是一个近似值,由于各种原因并不准确,其中一个原因是依赖于 Windows 消息传递模型,该模型的时间受到许多进程(包括您的进程)的影响,
    • 因此可能会导致进展不顺利。
  2. 在每个时间间隔重新计算已处理过渡的部分。这样一来,无论下一个间隔是否需要两倍以上,进度都将始终顺利。

首选第二种解决方案,这将转化为您正在寻找的以下一般例程。让我们从假设一个项目开始:

function InBetweenValue(BeginValue, EndValue: Byte; Duration, 
  StartTick: Cardinal): Byte
var
  Done: Single;
begin
  Done := (GetTickCount - StartTick) / Duration;
  if Done >= 1.0 then
    Result := EndValue
  else
    Result := Round(BeginValue + (EndValue - BeginValue) * Done);
end;

使用具有固定间隔的计时器是个好主意吗?

使用这种方法,计时器的间隔影响计算:在任何给定时间InBetweenValue 的结果都是正确的。 唯一需要 Timer 的事情是推动进度。如果您想要 67 Hz 的刷新率,请将其间隔设置为 15 毫秒。如果您想要 20 Hz 的刷新率,请将间隔设置为 50 毫秒。

性能

如果有很多值等待淡入淡出,会导致任何延迟吗?

不,不是出于隐含的原因。所有计算所需的时间可能取决于队列的大小,但这肯定不是一个重要因素。 (如果是这样,那么你的问题就更令人不安了)。由于错过或合并的 Windows 计时器消息,可能的“延迟”会以较低的刷新率表现出来,具体取决于计算机处理它正在执行的所有操作的繁忙程度。

数据存储

对值和时间使用动态数组(并在“StartFade”事件中填充它们并在淡入淡出完成后释放它们)是在黑暗中拍摄还是一个好的猜测?

我们先分析一下需要处理哪些数据。有一组任意长度的中间当前值,每个值都有自己的四个属性:开始值、结束值、过渡持续时间和过渡开始时间。因此,您可以选择:

  1. 存储 5 组:一组当前值和四组属性,或者
  2. 存储 1 组:一组当前值,其中每个值具有四个属性成员

第一个解决方案在保持所有五组同步方面遇到了麻烦。第二个需要另一个维度。我更喜欢后者。

您是否使用数组或其他东西取决于您。选择您最熟悉的、符合目的的或与输入或所需输出最匹配的。选择静态数组还是动态数组取决于输入的可变性,并且不会对性能产生可衡量的差异。动态数组需要运行时长度管理,而静态数组则不需要。

但既然您无论如何都需要动态解决方案,那么我建议您跳出框框思考。例如,RTL 没有为数组提供默认的内置管理工具,但它确实有可以提供的集合类,例如TList.

对于这个答案的其余部分,我将假设使用 Object 作为元素并使用 List 来跟踪它们的决定。

设计

现在两个最紧迫的点已经解决了,可以制定设计了。

有一个包含项目的List,每个项目都有它的当前值和四个属性:开始、结束、持续时间和开始时间。每个项目都必须能够获得新的属性值。有一个基于属性计算当前值的公式。还有一个 Timer 可以自动执行多个这些计算。

此外,应该为一个项目存储多个转换命令。由于我们已经有一个带有成员的 Item,让我们也将这些命令添加为 Item 的成员。

缺少什么?不,我们走吧。

实施

我们需要:

  • 具有两个成员的转换类型:结束值和持续时间,
  • 这些转换的多个类型,最好具有队列特征,
  • 具有六个成员的 Item 类型:开始值、结束值、持续时间、开始时间、当前值和转换,
  • 此类项目列表的类型,
  • 计算项目当前值的例程,
  • 当当前值达到结束值时弹出新转换的例程,
  • 执行此计算并弹出所有项目的例程,
  • 一个计时器来驱动这个整体例程,
  • 更新项目属性的例程。概括。我们需要设置所有属性的能力吗?过渡不是有所有需要的设置吗?
  • 将这一切结合在一起的 Object 类型。

这应该可以帮助您设置代码的接口部分。流连忘返,并渴望开始编写实现代码。

在此我的试用,起源如上所述:

unit Modulation;

interface

uses
  System.SysUtils, System.Classes, System.Generics.Collections, WinAPI.Windows,
  VCL.ExtCtrls;

type
  TTransition = record
    EndValue: Byte;
    Duration: Cardinal;
  end;

  TTransitions = class(TQueue<TTransition>);

  TByte = class(TObject)
  private
    FBeginValue: Byte;
    FCurrentValue: Byte;
    FEndValue: Byte;
    FDuration: Cardinal;
    FStartTick: Cardinal;
    FTransitions: TTransitions;
    procedure PopTransition;
  public
    procedure AddTransition(ATransition: TTransition);
    constructor Create;
    destructor Destroy; override;
    function HasTransition: Boolean;
    function InTransition: Boolean;
    procedure Recalculate;
    property CurrentValue: Byte read FCurrentValue;
  end;

  TBytes = class(TObjectList<TByte>);

  TByteModulator = class(TObject)
  private
    FItems: TBytes;
    FOnProgress: TNotifyEvent;
    FTimer: TTimer;
    function Finished: Boolean;
    function GetCurrentValue(Index: Integer): Byte;
    function GetItemCount: Integer;
    procedure SetItemCount(Value: Integer);
    procedure Proceed(Sender: TObject);
  protected
    procedure DoProgress;
  public
    procedure AddTransition(Index: Integer; ATransition: TTransition);
    constructor Create;
    destructor Destroy; override;
    property CurrentValues[Index: Integer]: Byte read GetCurrentValue; default;
    property ItemCount: Integer read GetItemCount write SetItemCount;
    property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
  end;

implementation

{ TByte }

procedure TByte.AddTransition(ATransition: TTransition);
begin
  if ATransition.Duration < 1 then
    ATransition.Duration := 1;
  FTransitions.Enqueue(ATransition);
  Recalculate;
end;

constructor TByte.Create;
begin
  inherited Create;
  FTransitions := TTransitions.Create;
  FDuration := 1;
end;

destructor TByte.Destroy;
begin
  FTransitions.Free;
  inherited Destroy;
end;

function TByte.HasTransition: Boolean;
begin
  Result := FTransitions.Count > 0;
end;

function TByte.InTransition: Boolean;
begin
  Result := FCurrentValue <> FEndValue;
end;

procedure TByte.PopTransition;
var
  Transition: TTransition;
begin
  Transition := FTransitions.Dequeue;
  FBeginValue := FCurrentValue;
  FEndValue := Transition.EndValue;
  FDuration := Transition.Duration;
  FStartTick := GetTickCount;
end;

procedure TByte.Recalculate;
var
  Done: Single;
begin
  Done := (GetTickCount - FStartTick) / FDuration;
  if Done >= 1.0 then
  begin
    FCurrentValue := FEndValue;
    if HasTransition then
      PopTransition;
  end
  else
    FCurrentValue := Round(FBeginValue + (FEndValue - FBeginValue) * Done);
end;

{ TByteModulator }

const
  RefreshFrequency = 25;

procedure TByteModulator.AddTransition(Index: Integer;
  ATransition: TTransition);
begin
  FItems[Index].AddTransition(ATransition);
  FTimer.Enabled := True;
end;

constructor TByteModulator.Create;
begin
  inherited Create;
  FItems := TBytes.Create(True);
  FTimer := TTimer.Create(nil);
  FTimer.Enabled := False;
  FTimer.Interval := MSecsPerSec div RefreshFrequency;
  FTimer.OnTimer := Proceed;
end;

destructor TByteModulator.Destroy;
begin
  FTimer.Free;
  FItems.Free;
  inherited Destroy;
end;

procedure TByteModulator.DoProgress;
begin
  if Assigned(FOnProgress) then
    FOnProgress(Self);
end;

function TByteModulator.Finished: Boolean;
var
  Item: TByte;
begin
  Result := True;
  for Item in FItems do
    if Item.InTransition or Item.HasTransition then
    begin
      Result := False;
      Break;
    end;
end;

function TByteModulator.GetCurrentValue(Index: Integer): Byte;
begin
  Result := FItems[Index].CurrentValue;
end;

function TByteModulator.GetItemCount: Integer;
begin
  Result := FItems.Count;
end;

procedure TByteModulator.Proceed(Sender: TObject);
var
  Item: TByte;
begin
  for Item in FItems do
    Item.Recalculate;
  DoProgress;
  FTimer.Enabled := not Finished;
end;

procedure TByteModulator.SetItemCount(Value: Integer);
var
  I: Integer;
begin
  for I := FItems.Count to Value - 1 do
    FItems.Add(TByte.Create);
  FItems.DeleteRange(Value, FItems.Count - Value);
end;

end.

还有一个小小的即插即用演示程序(注意标签只显示最后一个请求):

unit Unit2;

interface

uses
  System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms,
  VCL.ComCtrls, VCL.StdCtrls, Modulation;

type
  TForm2 = class(TForm)
  private
    FBars: array of TProgressBar;
    FLabels: array of TLabel;
    FByteModulator: TByteModulator;
    procedure FormClick(Sender: TObject);
    procedure Progress(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

{ TForm2 }

const
  Count = 10;

constructor TForm2.Create(AOwner: TComponent);
var
  I: Integer;
begin
  inherited Create(AOwner);
  FByteModulator := TByteModulator.Create;
  FByteModulator.ItemCount := Count;
  FByteModulator.OnProgress := Progress;
  SetLength(FBars, Count);
  SetLength(FLabels, Count);
  for I := 0 to Count - 1 do
  begin
    FBars[I] := TProgressBar.Create(Self);
    FBars[I].SetBounds(10, 10 + 30 * I, 250, 25);
    FBars[I].Smooth := True;
    FBars[I].Max := High(Byte);
    FBars[I].Parent := Self;
    FLabels[I] := TLabel.Create(Self);
    FLabels[I].SetBounds(270, 15 + 30 * I, 50, 25);
    FLabels[I].Parent := Self;
  end;
  OnClick := FormClick;
end;

destructor TForm2.Destroy;
begin
  FByteModulator.Free;
  inherited Destroy;
end;

procedure TForm2.FormClick(Sender: TObject);
var
  Transition: TTransition;
  Index: Integer;
begin
  Transition.EndValue := Random(High(Byte) + 1);
  Transition.Duration := Random(3000);
  Index := Random(Count);
  FLabels[Index].Caption := Format('%d > %d @ %f',
    [FByteModulator.CurrentValues[Index], Transition.EndValue,
    Transition.Duration / MSecsPerSec]);
  FByteModulator.AddTransition(Index, Transition);
end;

procedure TForm2.Progress(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
    FBars[I].Position := FByteModulator.CurrentValues[I];
end;

initialization
  Randomize;

end.

成功了。

【讨论】:

  • 好吧,TBH,从我的角度来看,它不是一个完整的程序,而只是一段代码,因为这是我正在(试图)创建的一部分作为“整个程序”;但是,您刚刚证明了“一段代码”部分的相反情况,这确实是一个非常大的问题,您显然,至少从我刚刚编译和运行的示例中看起来是这样,设法解决了!现在,请给我一天的时间来阅读您的代码并处理所有代码,直到理解为止,我会再次提出问题或简单的“谢谢”:) 但是,到目前为止,谢谢!
  • a'right,这就像我实际需要的那样工作。我确实更改了一些东西和参数以满足我的需要,但关键是你的核心原则适合我的需要!谢谢你。我是否得到您的许可才能使用它,并且如果将来需要,将它作为我的应用程序的一部分分发?当然,我很高兴将您命名为该应用的贡献者!
  • 该死,你花了一些时间来编辑我的问题。谢谢,我猜... Bdw,只是在调制单元中对您的答案代码提出警告:我认为将 TBytes 用于自定义类型并不是一个好主意,因为它是 System.SysUtils 中的保留类型...可能会给某人带来一些不兼容的麻烦(就像它对我所做的那样,直到我发现它是从 Modulation 而不是 SysUtils 声明的......);)
  • 命名由您决定。只要在uses子句中Modulation放在SysUtils之后,那么TBytes指的是Modulation.TBytes。无论如何,两者都可以通过限定命名空间来区分:(System.)SysUtils.TBytesModulation.TBytes。它不应该比想要Graphics.TBitmap 而不是Windows.TBitmap 带来更多问题,例如
  • 只是一个小问题;我似乎无法找到/编辑您从哪里获取 beginvalue... (使用相同的索引)?谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-23
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 2016-10-21
  • 1970-01-01
  • 2017-04-11
相关资源
最近更新 更多