skip to main |skip to sidebar

2007-10-21

[AS3]位图的色阶控制(2) -- 伽玛校正(Gamma Correction)

[AS3]位图的色阶控制(1) -- 得到色阶

位图调节色阶时,在内部使用的是曲线校正中的伽玛校正。

伽玛校正为调整亮度的方法,它只有一个参数即γ(Gamma)。伽玛校正的变换公式为:

X'=X_max*(x/X_max)^(1/γ)

x'为变换后的亮度。xmax为x的最大值。

γ = 1 x' = x (没有变化)
γ = 0.5 x' = x2(变暗)
γ = 2 x' = √x(变亮)

调整色阶的三个滑条的意思

Plotoshop的调整色阶里有黑,灰,白三个滑条,黑为阴影,白为高亮。从0到黑均为黑色;从白到255均为白色;从黑到白为以灰为中值的伽玛校正。(xb为黑,xg为灰,xw为白)

Actionscript 3.0的实现

实现曲线、伽玛校正等可以使用BitmapData.paletteMap。

三个滑条的值分别为xb、xg、xw (0≤xb≤xg≤xw≤255)。Xmax = xb - xw。当x = xg - xb时,因为想x' / xmax = 0.5,所以

g=( log(Xg-Xb / Xw-Xb) ) / (log(0.5)

var gamma:Number = Math.log((xg - xb) / (xw - xb)) / Math.log(0.5);
var mapR:Array = [], mapG:Array = [], mapB:Array = [];
for(var i:int = 0; i < 256; i++) {
 mapB[i] = i < xb ? 0 : i > xw ? 0xff : 255 * Math.pow((i - xb) / (xw - xb), 1 / gamma);
 mapG[i] = mapB[i] << 8;
 mapR[i] = mapB[i] << 16;
}
image.paletteMap(bmdOrigin, bmd.rect, new Point(), mapR, mapG, mapB);

完整的实现代码:


package bitmap {
 import flash.display.*;
 import flash.filters.*;
 import flash.geom.*;
 import flash.events.Event;

 [SWF(width="256", height="410")]
 public class Histogram3 extends Sprite {
  [Embed(source="023.jpg")]
  private var SampleImage:Class;

  private var dragging:Sprite;
  private var h2pos:Number = 0.5;
  private var h1:Sprite;
  private var h2:Sprite;
  private var h3:Sprite;

  public function Histogram3() {
   stage.scaleMode = "noScale";

   var bmd:BitmapData = Bitmap(addChild(new SampleImage())).bitmapData;

   var s:Sprite = new Sprite();
   addChild(s).y = bmd.height + 10;
   createHistogram(bmd, s);

   s = new Sprite();
   addChild(s).y = bmd.height + 140;
   createHistogram(bmd, s);

   addChild(createSlider()).y = bmd.height + 115;

   var bmdOrigin:BitmapData = bmd.clone();
   addEventListener(
    Event.ENTER_FRAME, 
    function(e:*):void {
     if(dragging) {
      var gamma:Number = Math.log((h2.x - h1.x) / (h3.x - h1.x)) / Math.log(0.5);
      var mapR:Array = [], mapG:Array = [], mapB:Array = [];
      for(var i:int = 0; i < 0x100; i++) {
       mapB[i] = i < h1.x ? 0 : i > h3.x ? 0xff : 255 * Math.pow((i - h1.x) / (h3.x - h1.x), 1 / gamma);
       mapG[i] = mapB[i] << 8;
       mapR[i] = mapB[i] << 16;
      }
      bmd.paletteMap(bmdOrigin, bmd.rect, new Point(), mapR, mapG, mapB);
      
      s.graphics.clear();
      createHistogram(bmd, s);
     }
    }
   );
  }

  // 生成色阶
  private function createHistogram(bmd:BitmapData, s:Sprite):void {
   // 灰度化
   var cmf:ColorMatrixFilter = new ColorMatrixFilter(
    [1 / 3, 1 / 3, 1 / 3, 0, 0, 
     1 / 3, 1 / 3, 1 / 3, 0, 0, 
     1 / 3, 1 / 3, 1 / 3, 0, 0]
   );
   var bmd2:BitmapData = bmd.clone();
   bmd2.applyFilter(bmd2, bmd2.rect, new Point(), cmf);

   // 用threshold来得到颜色分布
   var values:Array = [];
   for(var i:int = 0; i < 0x100; i++) {
    values[i] = bmd2.threshold(bmd2, bmd2.rect, new Point(), "==", 
     i + (i << 8) + (i << 16), 0, 0xffffff, false);
   }
   bmd2.dispose();

   // 画色阶
   var max:int = bmd.width * bmd.height / 50;
   s.graphics.lineStyle(1);
   for(i = 0; i < 0x100; i++) {
    s.graphics.moveTo(i, 100);
    s.graphics.lineTo(i, Math.max(0, 100 - values[i] / max * 100));
   }
  }

  // 生成滑条
  private function createSlider():Sprite {
   // 画滑条可移动的范围
   var slider:Sprite = new Sprite();
   slider.graphics.beginFill(0xffffff);
   slider.graphics.drawRect(0, 0, 256, 10);
   slider.graphics.endFill();
   slider.graphics.lineStyle(1, 0);
   slider.graphics.lineTo(255, 0);
   slider.buttonMode = true;
   slider.useHandCursor = true;

   // 画滑条
   h1 = Sprite(slider.addChild(createButton(0x000000))); h1.x = 0;
   h2 = Sprite(slider.addChild(createButton(0x999999))); h2.x = 128;
   h3 = Sprite(slider.addChild(createButton(0xffffff))); h3.x = 255;

   // mouseDown
   slider.addEventListener("mouseDown", function(e:*):void {
    var localX:Number = slider.globalToLocal(new Point(mouseX, mouseY)).x;

    // 滑动滑条
    var d1:Number = Math.abs(localX - h1.x);
    var d2:Number = Math.abs(localX - h2.x);
    var d3:Number = Math.abs(localX - h3.x);
    var max:Number = Math.min(d1, d2, d3);
    dragging = (max == d1 ? h1 : max == d2 ? h2 : h3);

    // 验证滑条是否超出范围
    var bounds:Rectangle = getDraggableBounds(dragging);
    dragging.x = Math.max(Math.min(localX, bounds.right), bounds.x);
    updateH2(null);

    dragging.startDrag(false, bounds);
   });

   // mouseMove
   stage.addEventListener("mouseMove", updateH2);

   // mouseUp
   stage.addEventListener("mouseUp", function(e:*):void {
    if(dragging) {
     dragging.stopDrag();
     dragging = null;
    }
   });

   return slider;
  }

  // 生成滑条
  private function createButton(color:int):Sprite {
   var s:Sprite = new Sprite();
   s.graphics.lineStyle(1, 0);
   s.graphics.beginFill(color);
   s.graphics.lineTo(5, 8.6);
   s.graphics.lineTo(-5, 8.6);
   s.graphics.endFill();
   return s;
  }

  // 计算滑条的移动范围
  private function getDraggableBounds(s:Sprite):Rectangle {
   if(s == h1) return new Rectangle(0, 0, h3.x - 4, 0);
   if(s == h2) return new Rectangle(h1.x + 2, 0, h3.x - h1.x - 4, 0);
   if(s == h3) return new Rectangle(h1.x + 4, 0, 255 - h1.x - 4, 0);
   return null;
  }

  // 计算灰滑条的移动
  private function updateH2(e:*):void {
   if(dragging && dragging != h2) {
    h2.x = (h3.x - h1.x) * h2pos + h1.x;
    h2.x = Math.max(Math.min(h2.x, h3.x - 2), h1.x + 2);
   }
   else if(dragging == h2){
    h2pos = (h2.x - h1.x) / (h3.x - h1.x);
   }
  }
 }
}

最终效果下载:Histogram2.swf

没有评论: