[FMX] FMX.MultiResBitmap 的一处改进

红鱼儿在其博客中发表的一篇文章中引述了官方QC中的一处缺陷报告,谈到了 TCustomMultiResBitmap.ItemByScale 的效率问题,并提供了一个优化。不过我觉得它的优化有点太偷懒了,而且一旦不匹配,效率还不如原来的实现。所以我简单审视了下 ItemByScale 的代码,实际上它的效率问题在于过度优化造成了负优化。如果在提供的图片特别多(比如成百上千个),它进行一遍比较并排序还是有意义的,但现实是我们每个程序中实际使用的图数量是有限的,一般只有几个,用排序后再比较的算法明显是过度的优化反而降低了效率(我也没少干这事:-:)。

说了人家那么多,不提供一个自己的实现好象说不过去。实际上我想提供一个带缓存的版本,后来觉得先不得了,提供一个简单改进版本吧,这个版本在测试的几种情况下都明显优于原来的实现,一般是在4-10倍左右。

function TCustomMultiResBitmap.ItemByScale(const AScale: Single;
  const ExactMatch: Boolean; const IncludeEmpty: Boolean): TCustomBitmapItem;
var
  LScale, LCurrentScale: Single;
  I, LCount: Integer;
  //Added by swish
  LDelta,LMinDelta: Single;
  AItem: TCustomBitmapItem;
  //Comment by swish
//  LScaleArray: TArray<Single>;
begin
  LScale := RoundTo(AScale, TCustomBitmapItem.ScaleRange);
  Result := nil;
  if not ExactMatch then
  begin
    //Replaced by swish
      LCount:=-1;
      LMinDelta:=MaxInt;
      for I := 0 to Count - 1 do
      begin
      AItem:=Items[I];
      if (not IncludeEmpty) and AItem.IsEmpty then
        Continue;
      LDelta:=AItem.Scale-LScale;
      if LDelta<0 then
        LDelta:=-LDelta;
      if LDelta<LMinDelta then
        begin
        LMinDelta:=LDelta;
        LCount:=I;
        if (LMinDelta=0) or ((LMinDelta>0) and (LMinDelta<TEpsilon.Scale)) then
          Break
        end;
      end;
      if LCount>=0 then
        Result:=Items[LCount];
      Exit;

    //Follow is origin codes
//     LScaleArray := ScaleArray(IncludeEmpty);
//     LCount := Length(LScaleArray);
//     if LCount > 0 then
//     begin
//       I := -1;
//       repeat
//       Inc(I)
//       until (LScale < LScaleArray[I]) or (I >= LCount - 1);
//       if I > 0 then
//       begin
//       LCurrentScale := LScaleArray[I - 1] + (LScaleArray[I] - LScaleArray[I - 1]) / 5;
//       if LScale <= LCurrentScale then
//       Dec(I);
//       end;
//       LScale := LScaleArray[I];
//     end;

  end;
  for I := 0 to Count - 1 do
    if SameValue(LScale, Items[I].Scale, TEpsilon.Scale) and
      (IncludeEmpty or (not Items[I].IsEmpty)) then
    begin
      Result := Items[I];
      Exit;
    end
end;

使用新的 TCustomMultiResBitmap 的方法参考红鱼儿的原博客,我就不多说了。实际上,如果进一步优化的话,官方应该将SameValue 改成内联,另外,象 Items[I]  这种本质上对函数的调整还是应该缓存到一个变量里。真正彻底的优化最好就是在维护不同分辨率图片时,内部按缩放比例进行排序,这样在这里就可以直接二分找到最接近缩放比的图片了,不过那个留给官方去改吧。说不定官方有更好的算法呢。

分享到: