In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

JANコードリーダー + Yahoo! shopping API

車輪の再発明ってことでバーコードリーダー作ってみました。

webカメラからJANコード(書籍ならISBNコード)を読み取って
Yahoo!ショッピングへのリンクを表示するよ。

「JANコード以外にも、このバーコードが読めるようになると面白いことが出来るよ!」
っていうのがあったら是非教えて下さい。
...
@author @kndys
Get Adobe Flash player
by knd 02 Nov 2009
package  
{
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BlendMode;
	import flash.display.Graphics;
	import flash.display.Loader;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.filters.BlurFilter;
	import flash.geom.Rectangle;
	import flash.media.Camera;
	import flash.media.Video;
	import flash.net.navigateToURL;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.utils.ByteArray;
	
	[SWF(width="465",height="465")]	
	/**
	 * 車輪の再発明ってことでバーコードリーダー作ってみました。
	 * 
	 * webカメラからJANコード(書籍ならISBNコード)を読み取って
	 * Yahoo!ショッピングへのリンクを表示するよ。
	 * 
	 * 「JANコード以外にも、このバーコードが読めるようになると面白いことが出来るよ!」
	 * っていうのがあったら是非教えて下さい。
	 * ...
	 * @author @kndys
	 */
	public class BarcodeReader extends Sprite
	{
		
		private var _w:Number = stage.stageWidth;
		private var _h:Number = stage.stageHeight;
		private var info:Sprite;
		private var button:Sprite;
		private var imageLoader:Loader;
		private var infoText:TextField;
		private var format:TextFormat;
		
		public function BarcodeReader() 
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point

                        //Wonderfl.disable_capture( );
                        Wonderfl.capture_delay(30);
                        
			//画面の部品もろもろ
			info = new Sprite();
			info.y = _h * 0.5;
			info.graphics.beginFill(0);
			info.graphics.drawRect(0, 0, _w, 0.5 * _h);
			info.graphics.endFill();
			addChild(info);
			button = new Sprite();
			button.buttonMode = true;
			imageLoader = new Loader();
			infoText = new TextField();
			infoText.autoSize = TextFieldAutoSize.LEFT;
			format = new TextFormat(null, 11, 0xffffff);
			button.addChild(imageLoader);
			info.addChild(button);
			info.addChild(infoText);
			
			//カメラのセッティング
			var center:int = _h * 0.25;
			blendMode = BlendMode.LAYER;
			var camera:Camera = Camera.getCamera();
			camera.setMode(640, 240, 30);
			var video:Video = new Video(640, 240);
			video.attachCamera(camera);
			var bmd:BitmapData = new BitmapData(video.width, video.height);
			var bitmap:Bitmap = new Bitmap(bmd);
			addChild(bitmap);
			
			//バーコードリーダーらしい只の演出
			var redLineLayer:Shape = new Shape();
			redLineLayer.filters = [new BlurFilter(0, 8)];
			var redLine:Graphics = redLineLayer.graphics;
			redLine.lineStyle(4, 0xff0000, 0.99);
			redLine.moveTo(0, center);
			redLine.lineTo(_w, center);
			redLineLayer.blendMode = BlendMode.ADD;
			addChild(redLineLayer);
			
			var ranges:Vector.<uint>;
			var rect:Rectangle = new Rectangle(0, center-2, _w, 5);
			var bc:Barcode = new Barcode(); //その実態は只の2値化データ
			bc.lines = 5; //ライン本数。
			bc.threshold = 8; //色が変わったと見なす最小変化量
			var jan:JANcoder = new JANcoder();
			jan.toloerance = 0.35; //許容誤差。0.5超えると明らかに誤読が増える。
			var str:String;
			stage.addEventListener(MouseEvent.CLICK, function(mouse:MouseEvent):void {
				if (!hasEventListener(Event.ENTER_FRAME))
				{
					addEventListener(Event.ENTER_FRAME, function(event:Event):void {
						bmd.lock();
						bmd.draw(video);
						bc.pixels = bmd.getPixels(rect);
						bmd.setPixels(rect, bc.pixels);
						bmd.unlock();
						str = jan.decode(bc);
						if (str)
						{
							var barcodeRect:Rectangle = new Rectangle( 10, 10, 220, 60);
							var barcodePixels:ByteArray = new ByteArray();
							//strをエンコードして高さ1幅任意のビットマップデータを作る
							var barcodeLine:ByteArray = jan.encode(str, barcodeRect.width).pixels;
							for (var n:int = 0; n< barcodeRect.height; n+=1) 
							{
								barcodeLine.position = 0;
								barcodePixels.writeBytes(barcodeLine);
							}
							barcodePixels.position = 0;
							bmd.setPixels(barcodeRect, barcodePixels);
							event.target.removeEventListener(Event.ENTER_FRAME, arguments.callee);
							onDetectCode(str);
						}
					});
				}
			});
			stage.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
		}
		
		private function onDetectCode(code:String):void
		{
			var appId:String = "S8Xwcuixg64G.378PP9nnSwer3_ijeXhTfUcOCnDyIZVSxgCFivokE5SIhJ00QXuC4uoXk69";
			var url:String = "http://shopping.yahooapis.jp/ShoppingWebService/V1/itemSearch?appid=" +
				appId + "&jan=" + code;
			var textLoader:URLLoader = new URLLoader();
			textLoader.addEventListener(Event.COMPLETE, function(event:Event):void {
				var yahoo :ItemSearchHits = new ItemSearchHits(XML(textLoader.data));
				var lowest:Object = yahoo.getLowestPrice();
				if (lowest)
				{
					if(lowest.imageMediumURL || lowest.imageSmallURL)
					{
						loadImage(lowest);
					}
				}
				else
				{
					infoText.text = "ヒットしませんでした"
					infoText.setTextFormat(format);
					infoText.x = 0.5 * (info.width - infoText.textWidth);
					infoText.y = 0.5 * (info.height - infoText.textHeight);
				}
			});
			textLoader.load(new URLRequest(url));
		}
		private var buttonFunction:Function = null;
		private function loadImage(hitObject:Object):void
		{
			if (buttonFunction != null) button.removeEventListener(MouseEvent.CLICK, buttonFunction);
			buttonFunction = function(evt:MouseEvent):void {
				navigateToURL(new URLRequest(hitObject.url), "_blank");
			}
			button.addEventListener(MouseEvent.CLICK, buttonFunction);
			
			var container:Sprite = info;
			imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(evt:Event):void {
				button.scaleX = button.scaleY = 200 / imageLoader.height;
				button.x = (0.5 * _w - button.width - 10);
				button.y = 0.5 * (0.5 * _h - button.height);
				infoText.text = String(hitObject["name"]) + "\n\n価格: " + String(hitObject.price) + "円\n\n code: " +
					String(hitObject.code) + "\n\n" + 
					(Boolean(hitObject.isAvailable)? "購入できます": "購入できません");
				infoText.setTextFormat(format);
				infoText.x = button.x + button.width + 20;
				infoText.y = button.y + 10;
				evt.target.removeEventListener(Event.COMPLETE, arguments.callee);
			});
			imageLoader.load(new URLRequest(
				hitObject.imageMediumURL? hitObject.imageMediumURL: hitObject.imageSmallURL
			));
		}
		
	}
	
}
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	class Barcode
	{
		private var _pixels:ByteArray;
		private var _data:ByteArray;
		private var _lines:uint = 1;
		private var _threshold:uint = 0;
		private var redVals:Vector.<int>;
		private var redGrads:Vector.<int>;

		public function Barcode() 
		{
			_pixels = new ByteArray();
			_data = new ByteArray();
			redVals = new Vector.<int>();
			redGrads = new Vector.<int>();
		}
		
		public function set pixels(value:ByteArray):void 
		{
			_pixels.clear();
			_data.clear();
			redVals.splice(0, redVals.length);
			redGrads.splice(0, redGrads.length);
			value.position = 0;
			_pixels.writeBytes(value);
			if (_pixels.length < 7)
			{
				return ;
			}
			var length:uint = _pixels.length >>> 2;
			var lineLength:uint = length / _lines;
			_pixels.position = 0;
			var i:int;
			var argb:uint;
			for (i=0; i < length; i += 1)
			{
				argb = _pixels.readUnsignedInt();
				//赤チャネルと緑チャネルを2:1で混合してる。比率はほぼ無根拠。
				redVals.push(((argb >>> 15) & 0x1fe ) + ((argb>>>8) & 0xff));
			}
			redGrads.push(0); //1lineあたりの要素数を合わせる
			redGrads.push(0);
			for (i = 4; i < length; i += 1)
			{
				redGrads.push(-redVals[i - 4] - (redVals[i - 3] << 1) + (redVals[i - 1] << 1) + redVals[i]);
			}
			redGrads.push(0);
			redGrads.push(0); //1lineあたりの要素数を合わせる
			var maxGrad0:int = 0;
			var minGrad0:int = 0;
			var maxGrad:int = 0;
			var minGrad:int = 0;
			var lstGrad:int = 0;
			var curGrad:int = 0;
			var nexGrad:int = 0;
			var state:int = 1; //現在の線の明暗を示す。黒なら+1、白なら-1。
			//まず中央から左に向かってスキャンする
			for (i = lineLength >> 1 ; i > 0; i -= 1)
			{
				nexGrad = curGrad;
				curGrad = lstGrad;
				lstGrad = redGrads[i];
				//極大候補発見
				if (curGrad > lstGrad && curGrad >= nexGrad)
				{
					//極大基準値より大きかったら
					if (curGrad > maxGrad0)
					{
						//極大値を更新する
						maxGrad0 = (curGrad >> 2) + _threshold;
						minGrad0 = -maxGrad0;
					}
				}
				//極小候補発見
				else if (curGrad < lstGrad && curGrad <= nexGrad)
				{
					//極小基準値より小さかったら
					if (curGrad < minGrad0)
					{
						//極小値を更新
						minGrad0 = (curGrad >> 2) - _threshold;
						maxGrad0 = minGrad0;
					}
				}
			}
			maxGrad = maxGrad0;
			minGrad = minGrad0;
			
			//次に左端よりスキャン
			var lastWidth:Number = 0.0;
			var barWidth:Number = 1.0;//左端の1ピクセル
			var peak:Number;
			curGrad = 0;
			nexGrad = 0;
			for (i = 2; i < length; i += 1 )
			{
				
				if (i % lineLength == 0)
				{
					_data.writeDouble(lastWidth);
					barWidth += 1.0;//右端の1ピクセル
					_data.writeDouble(barWidth);
					if (state < 0) 
					{
						state = 1;
						_data.writeDouble(0.0);
					}
					lastWidth = 0.0;
					barWidth = 1.0;//左端の1ピクセル
					curGrad = 0;
					nexGrad = 0;
					maxGrad = maxGrad0;
					minGrad = minGrad0;
					i += 2;
				}
				
				lstGrad = curGrad;
				curGrad = nexGrad;
				nexGrad = redGrads[i];
				//極大基準値より大きかったら
				if (curGrad > lstGrad && curGrad >= nexGrad && curGrad > maxGrad)
				{
					//極大値を更新する
					maxGrad = (curGrad >> 2) + _threshold;
					minGrad = -maxGrad;
					peak = calPeak(lstGrad, curGrad, nexGrad);
					barWidth += (0.5 + peak);
					//sum += barWidth;
					if (state < 0)
					{
						//もし白線に切り替わった後だったら、前回の結果に足す
						lastWidth += barWidth;
					}
					else
					{
						_data.writeDouble(lastWidth);
						lastWidth = barWidth;
						state = -1;
					}
					barWidth = 0.5 - peak;
				}
				//極小基準値より小さかったら
				else if (curGrad < lstGrad && curGrad <= nexGrad && curGrad < minGrad)
				{
					//極小値を更新
					minGrad = (curGrad >> 2) - _threshold;
					maxGrad = -minGrad;
					peak = calPeak(lstGrad, curGrad, nexGrad);
					barWidth += (0.5 + peak);
					//sum += barWidth;
					if (state > 0)
					{
						//もし黒線に切り替わった後だったら、前回の結果に足す
						lastWidth += barWidth;
					}
					else
					{
						_data.writeDouble(lastWidth);
						lastWidth = barWidth;
						state = 1;
					}
					barWidth = 0.5 - peak;
				}
				else
				{
					barWidth += 1.0;
				}
			}
			_data.writeDouble(lastWidth);
			barWidth += 1.0;//右端の1ピクセル
			_data.writeDouble(barWidth);
		}
		
		private function calPeak(yDec:Number, y:Number, yInc:Number):Number
		{
			var _2b:Number = yInc - yDec ;
			var _2a:Number = yInc + yDec - 2 * y;
			return -0.5 * _2b / _2a;
		}

		
		public function get pixels():ByteArray
		{
			var value:ByteArray = new ByteArray();
			var binColor:uint = 0xff000000;
			var grayScale:uint;
			var lim:uint;
			var dec:Number;
			var rem:Number = 0;
			var i :uint ;
			var length:uint = _data.length >>> 3;
			var barWidth:Number;
			_data.position = 0;
			for (i = 0; i < length; i+=1 ) 
			{
				if (dec > 0.000001)//丸め誤差対策
				{
					rem = 1.0 - dec;
					grayScale = 0xff & ((Number(binColor & 0xff) * dec + Number( (~binColor) & 0xff) * rem + 0.5) >> 0);
					grayScale = 0xff000000 | (grayScale << 16) | (grayScale << 8) | grayScale; 
					value.writeUnsignedInt(grayScale);
				}
				else
				{
					rem = 0.0;
				}
				barWidth = _data.readDouble();
				barWidth -= rem;
				lim = barWidth >> 0; //バー幅の整数部
				dec = barWidth - lim; //小数部
				binColor ^= 0x00ffffff; //RGB反転
				for (; lim > 0;lim-=1 )
				{
					value.writeUnsignedInt(binColor);
				}
			}
			if (dec > 0.5)
			{
				value.writeUnsignedInt(binColor);
			}
			value.position = 0;
			return value;
		}
		
		
		public function get data():ByteArray 
		{
			_data.position = 0;
			var value:ByteArray = new ByteArray();
			value.writeBytes(_data);
			value.position = 0;
			return value; 
		}
		
		public function set data(value:ByteArray):void 
		{
			_data.clear();
			value.position = 0;
			_data.writeBytes(value);
			_data.position = 0;
		}
		
		public function get lines():uint { return _lines; }
		
		public function set lines(value:uint):void 
		{
			_lines = value;
		}
		
		public function get threshold():uint { return _threshold >> 3; }
		
		public function set threshold(value:uint):void 
		{
			_threshold = value << 3;
		}
	}
	
	class JANcoder
	{
		private var _toloerance:Number = 0.2;
		private var toleranceMax:Number = 1.2;
		private var toleranceMin:Number = 0.8;
		private var dic:Dictionary;
		private const UPSIDE_DOWN:String = "!";
		private const leftOddPatterns:Vector.<uint> = new Vector.<uint>();
		private const leftEvenPatterns:Vector.<uint> = new Vector.<uint>();
		//private const rightOddPatterns:Vector.<uint> = new Vector.<uint>();
		private const rightEvenPatterns:Vector.<uint> = new Vector.<uint>();
		private const firstCharPatterns:Vector.<uint> = new Vector.<uint>();
		public function JANcoder() 
		{
			dic = new Dictionary();
			//↓クリップボードに入れて使ったスニペット。便利!
			//dic[$$(pat)] = new Code($$(pat), "$$(char)", true, true);
			//左側奇数パリティパターン
			dic[0x0d] = new Code(0x0d, "0", true, true);
			dic[0x19] = new Code(0x19, "1", true, true);
			dic[0x13] = new Code(0x13, "2", true, true);
			dic[0x3d] = new Code(0x3d, "3", true, true);
			dic[0x23] = new Code(0x23, "4", true, true);
			dic[0x31] = new Code(0x31, "5", true, true);
			dic[0x2f] = new Code(0x2f, "6", true, true);
			dic[0x3b] = new Code(0x3b, "7", true, true);
			dic[0x37] = new Code(0x37, "8", true, true);
			dic[0x0b] = new Code(0x0b, "9", true, true);
			leftOddPatterns.push(0x0d, 0x19, 0x13, 0x3d, 0x23, 0x31, 0x2f, 0x3b, 0x37, 0x0b); 
			//左側偶数パリティパターン
			dic[0x27] = new Code(0x27, "0", true, false);
			dic[0x33] = new Code(0x33, "1", true, false);
			dic[0x1b] = new Code(0x1b, "2", true, false);
			dic[0x21] = new Code(0x21, "3", true, false);
			dic[0x1d] = new Code(0x1d, "4", true, false);
			dic[0x39] = new Code(0x39, "5", true, false);
			dic[0x05] = new Code(0x05, "6", true, false);
			dic[0x11] = new Code(0x11, "7", true, false);
			dic[0x09] = new Code(0x09, "8", true, false);
			dic[0x17] = new Code(0x17, "9", true, false);
			leftEvenPatterns.push(0x27, 0x33, 0x1b, 0x21, 0x1d, 0x39, 0x05, 0x11, 0x09, 0x17);
			//右側偶数パリティパターン
			dic[0x72] = new Code(0x72, "0", false, false);
			dic[0x66] = new Code(0x66, "1", false, false);
			dic[0x6c] = new Code(0x6c, "2", false, false);
			dic[0x42] = new Code(0x42, "3", false, false);
			dic[0x5c] = new Code(0x5c, "4", false, false);
			dic[0x4e] = new Code(0x4e, "5", false, false);
			dic[0x50] = new Code(0x50, "6", false, false);
			dic[0x44] = new Code(0x44, "7", false, false);
			dic[0x48] = new Code(0x48, "8", false, false);
			dic[0x74] = new Code(0x74, "9", false, false);
			rightEvenPatterns.push(0x72, 0x66, 0x6c, 0x42, 0x5c, 0x4e, 0x50, 0x44, 0x48, 0x74);
			//右側奇数パリティパターン
			//本来右側に奇数パリティが現れることは無いので
			//左側が全て偶数パリティならバーコードは逆さま
			dic[0x58] = new Code(0x58, "0", false, true);
			dic[0x4c] = new Code(0x4c, "1", false, true);
			dic[0x64] = new Code(0x64, "2", false, true);
			dic[0x5e] = new Code(0x5e, "3", false, true);
			dic[0x62] = new Code(0x62, "4", false, true);
			dic[0x46] = new Code(0x46, "5", false, true);
			dic[0x7a] = new Code(0x7a, "6", false, true);
			dic[0x6e] = new Code(0x6e, "7", false, true);
			dic[0x76] = new Code(0x76, "8", false, true);
			dic[0x68] = new Code(0x68, "9", false, true);
			//左側6文字から最初の文字を決定する
			//(isOdd?1:0)として
			dic[0xbf] = new Code(0x3f, "0", true, true);
			dic[0xb4] = new Code(0x34, "1", true, true);
			dic[0xb2] = new Code(0x32, "2", true, true);
			dic[0xb1] = new Code(0x31, "3", true, true);
			dic[0xac] = new Code(0x2c, "4", true, true);
			dic[0xa6] = new Code(0x26, "5", true, true);
			dic[0xa3] = new Code(0x23, "6", true, true);
			dic[0xaa] = new Code(0x2a, "7", true, true);
			dic[0xa9] = new Code(0x29, "8", true, true);
			dic[0xa5] = new Code(0x25, "9", true, true);
			dic[0x80] = new Code(0x80, UPSIDE_DOWN, true, true);
			firstCharPatterns.push(0x3f, 0x34, 0x32, 0x31, 0x2c, 0x26, 0x23, 0x2a, 0x29, 0x25, 0x80);
		}
		
		public function get toloerance():Number { return _toloerance; }
		
		public function set toloerance(value:Number):void 
		{
			_toloerance = value;
			toleranceMax = 1 + _toloerance;
			toleranceMin = 1 - _toloerance;
		}
		
		private function nearlyEqual(x:Number, y:Number):Boolean
		{
			var ratio:Number = y / x;
			return (ratio > toleranceMin && ratio < toleranceMax);
		}
		
		private function generatePattern(isLefty:Boolean,
			w1:Number, w2:Number, w3:Number, w4:Number):uint
		{
			var sum:Number = w1 + w2 + w3 + w4;
			var wid:uint = 7;
			var k:Number = wid / sum;
			var pos:Vector.<uint> = new Vector.<uint>();
			pos.push((k * w1 + 0.5) >> 0);
			sum -= w1;
			wid -= pos[0];
			k = wid / sum;
			pos.push((k * w2 + 0.5) >> 0);
			sum -= w2;
			wid -= pos[1];
			k = wid / sum;
			pos.push((k * w3 + 0.5) >> 0);
			sum -= w3;
			wid -= pos[2];
			k = wid / sum;
			pos.push((k * w4 + 0.5) >> 0);
			var pattern:uint = 0;
			var bit:uint = 0;
			if (!isLefty)
			{
				bit ^= 1;
			}
			for (var i:int = 0; i< 4; i+=1) 
			{
				for (var j:int = 0; j < pos[i]; j+=1 ) 
				{
					pattern <<= 1;
					pattern |= bit;
				}
				bit ^= 1;
			}
			return pattern;
		}
		
		private function isLeftBars(space1:Number, bar1:Number, space2:Number, bar2:Number):Boolean
		{
			var min:Number = Math.min(bar1, space2, bar2);
			var max:Number = Math.max(bar1, space2, bar2);
			return max < 0.2 * space1 && nearlyEqual(min , max); 
		}
		private function isCenterBars(
			space1:Number, bar1:Number, space2:Number, bar2:Number, space3:Number):Boolean
		{
			var min:Number = Math.min(space1, bar1, space2, bar2, space3);
			var max:Number = Math.max(space1, bar1, space2, bar2, space3);
			return nearlyEqual(min, max); 
		}
		private function isRightBars(bar1:Number, space1:Number, bar2:Number, space2:Number):Boolean
		{
			var min:Number = Math.min(bar1, space1, bar2);
			var max:Number = Math.max(bar1, space1, bar2);
			return max < 0.2 * space2 && nearlyEqual(min , max); 
		}
		
		private function checkDigits(codes:Vector.<Code>):Boolean
		{
			if (codes.length != 13) return false;
			var sum:int;
			for (var i:int = 0; i< 12; i+=1) 
			{
				sum += (1 + 2 * (i % 2)) * parseInt(codes[i].character, 10);
			}
			return parseInt(codes[12].character, 10) == (10 - sum % 10) % 10;
		}
		
		public function decode(barcode:Barcode):String
		{
			var data:ByteArray = barcode.data;
			var length:uint = data.length;
			if (length < 45) //JANコードを表現出来るだけの線の数が無ければ即終了
			{
				return null;
			}
			var codes:Vector.<Code>;
			var space1:Number;
			var bar1:Number;
			var space2:Number;
			var bar2:Number;
			var space3:Number;
			var code:Code;
			var str:String;
			var parities:uint;
			var readingPosition:uint;
			var i:uint;
			data.position = 0;
			//JANコードが見つかるまでこのループからは出られない。
			MAIN_LOOP:
			while(true)
			{
				space1 = data.readDouble();
				if (data.position == length) { return null; }
				bar1 = data.readDouble();
				readingPosition = data.position; //読み取れなかったらここに戻る
				if (data.position == length) { return null; }
				space2 = data.readDouble();
				if (data.position == length) { return null; }
				bar2 = data.readDouble();
				if (data.position == length) { return null; }
				if (isLeftBars(space1, bar1, space2, bar2))
				{
					//Vectorの生成コストを抑えるために、必要になってから生成。
					if (!(codes)) { codes = new Vector.<Code>(); }
					//Vectorが存在していたら空にする。
					else {	codes.splice(0, codes.length); }
					LEFT:
					while (true)
					{
						space1 = data.readDouble();
						if (data.position == length) { return null; }
						bar1 = data.readDouble();
						if (data.position == length) { return null; }
						space2 = data.readDouble();
						if (data.position == length) { return null; }
						bar2 = data.readDouble();
						if (data.position == length) { return null; }
						space3 = data.readDouble();
						if (data.position == length) { return null; }
						if (isCenterBars(space1, bar1, space2, bar2, space3)) break LEFT;
						code = dic[generatePattern(true, space1, bar1, space2, bar2)];
						if (!code || !(code.isLefty)) //もしパターンが辞書にないか、右側のパターンだったら
						{
							// 若干黒線が細かったとしたら
							code = dic[generatePattern(true, space1 * toleranceMin, bar1 * toleranceMax,
								space2 * toleranceMin, bar2 * toleranceMax)];
							if (!code || !(code.isLefty))
							{
								//若干黒線が太かったとしたら
								code = dic[generatePattern(true, space1 * toleranceMax, bar1 * toleranceMin,
									space2 * toleranceMax, bar2 * toleranceMin)];
								if (!code || !(code.isLefty))
								{
									data.position = readingPosition;
									continue MAIN_LOOP;
								}
							}
						}
						codes.push(code);
						data.position -= 8;
					} // LEFT
					RIGHT:
					while (true)
					{
						bar1 = data.readDouble();
						if (data.position == length) { return null; }
						space1 = data.readDouble();
						if (data.position == length) { return null; }
						bar2 = data.readDouble();
						if (data.position == length) { return null; }
						space2 = data.readDouble();
						if (data.position == length) { return null; }
						if (isRightBars(bar1, space1, bar2, space2)) break RIGHT;
						code = dic[generatePattern(false, bar1, space1, bar2, space2)];
						if (!code || code.isLefty) //もしパターンが辞書にないか、左側のパターンだったら
						{
							code = dic[generatePattern(false, bar1 * toleranceMax, space1 * toleranceMin,
								bar2 * toleranceMax, space2 * toleranceMin)];
							if (!code || code.isLefty)
							{
								code = dic[generatePattern(false, bar1 * toleranceMin, space1 * toleranceMax,
									bar2 * toleranceMin, space2 * toleranceMax)];
								if (!code || code.isLefty)
								{
									data.position = readingPosition;
									continue MAIN_LOOP;
								}
							}
						}
						codes.push(code);
					} // RIGHT
					parities = 0;
					for (i = 0; i < 6; i++)
					{
						if (!(codes[i].isLefty))
						{
							//初めの6文字の中に右側のコードが混ざったらやり直し
							continue MAIN_LOOP;
						}
						parities <<= 1;
						if (codes[i].isOdd)
						{
							parities |= 1;
						}
					}
					if (parities == 0) //パリティが全て偶数ならバーコードは逆さま
					{
						codes.reverse();
						parities = 0;
						for (i = 0; i < 6; i++)
						{
							if ((codes[i].isLefty))
							{
								//初めの6文字の中に左側のコードが混ざったらやり直し
								continue MAIN_LOOP;
							}
							parities <<= 1;
							if (codes[i].isOdd)
							{
								parities |= 1;
							}
						}
					}
					code = dic[parities | 0x80];
					if (code) 
					{
						codes.unshift(code);
					}
					if (checkDigits(codes))
					{
						break MAIN_LOOP;
					}
					
				} // if(isLeftBar) 
				//JANコードが見つからなかった場合ここを通ってMAIN_LOOPに戻る
				data.position = readingPosition;
			} // MAIN_LOOP
			//JANコード発見&ループ脱出
			str = "";
			for each(code in codes)
			{
				str += code.character;
			}
			//trace(str);
			return str;
		}
		
		public function encode(codeNumbers:String, lineWidth:uint):Barcode
		{
			//dataが数字x13の繰り返しでなければnull返す。
			if (!(/^\D*(?:(\d)\D*){13}$/.test(codeNumbers))) { return null; }
			codeNumbers = codeNumbers.replace(/\D/g, "");
			
			var i:int;
			var num:uint = parseInt(codeNumbers.charAt(i));
			var parities:Vector.<Boolean> = new Vector.<Boolean>();
			
			var msk:uint = 0x20;
			var pat:uint = firstCharPatterns[num];
			for (i = 0; i < 6; i += 1) 
			{
				parities.push((pat & msk) != 0);
				msk >>>= 1;
			}
			
			var patterns:Vector.<uint> = new Vector.<uint>();
			for (i = 1; i< 7; i+=1) 
			{
				num = parseInt(codeNumbers.charAt(i));
				if (parities[i-1]) { patterns.push(leftOddPatterns[num]); }
				else { patterns.push(leftEvenPatterns[num]); }
			}
			for (i = 7; i < 13; i += 1)
			{
				num = parseInt(codeNumbers.charAt(i));
				patterns.push(rightEvenPatterns[num]);
			}
			
			var unit:Number = lineWidth / 115;// 115 = 13 + 7*6 + 5 + 7*6 + 13
			var data:ByteArray = new ByteArray();
			data.clear();
			// write left guard bars
			data.writeDouble(unit * 10);
			data.writeDouble(unit);
			data.writeDouble(unit);
			data.writeDouble(unit);
			var j:int;
			var state:Boolean = false;
			var wid:Number;
			for (i = 0; i < 12; i += 1)
			{
				if (i == 6) // write center bars
				{
					data.writeDouble(unit);
					data.writeDouble(unit);
					data.writeDouble(unit);
					data.writeDouble(unit);
					data.writeDouble(unit);
					state = !state;
				}
				msk = 0x40;
				pat = patterns[i];
				wid = 0.0;
				for (j = 0; j < 7; j += 1)
				{
					if (((msk & pat) != 0) == state)
					{
						wid += unit;
					}
					else
					{
						data.writeDouble(wid);
						state = !state;
						wid = unit;
					}
					msk >>>= 1;
				}
				data.writeDouble(wid);
				state = !state;
			}
			// write right guard bars
			data.writeDouble(unit);
			data.writeDouble(unit);
			data.writeDouble(unit);
			data.writeDouble(unit * 10);
			data.position = 0;
			
			var barcode:Barcode = new Barcode();
			barcode.data = data;
			return barcode;
		}
		
	}

	class Code
	{
		public var pettern:uint;
		public var character:String;
		public var isLefty:Boolean;
		public var isOdd:Boolean;
		public function Code(pettern:uint, char:String,isLefty:Boolean, isOdd:Boolean)
		{
			this.pettern = pettern;
			this.character = char;
			this.isLefty = isLefty;
			this.isOdd = isOdd;
		}
	}
	
	class ItemSearchHits
	{
		private var list:XMLList;
		private var hits:Vector.<Object>;
		
		public function ItemSearchHits(resultList:XML) 
		{
			var ns:Namespace = resultList.namespace();
			default xml namespace = ns;
			if (resultList.@totalResultsReturned == "0") return;
			list = resultList..Hit;
			hits = new Vector.<Object>();
			var obj:Object;
			for each (var hit:XML in list) 
			{
				obj = {
					"index" : hit.@index.toString(),
					"name" : hit.Name.toString(),
					"url" : hit.Url.toString(),
					"isAvailable" : hit.Availability.toString() == "instock",
					"imageSmallURL" : hit.Image.Small.toString(),
					"imageMediumURL" : hit.Image.Medium.toString(),
					"price" : parseInt(hit.Price.toString()),
					"code" : (hit.JanCode?hit.JanCode.toString():hit.IsbnCode.toString())
				};
				hits.push(obj);
			}
		}
		public function toString():String 
		{
			var str:String = "";
			for each(var obj:Object in hits) 
			{
				str += "{"
				for (var prop:String in obj)
				{
					str += prop + ": " + obj[prop] + ", ";
				}
				str += "}\n";
			}
			return str;
		}
		public function getLowestPrice(onlyAvailable:Boolean = false):Object
		{
			var price:int = int.MAX_VALUE;
			var result:Object;
			for each( var obj:Object in hits)
			{
				if ((!onlyAvailable || obj.isAvailable) && obj.price < price)
				{
					result = obj;
					price = obj.price;
				}
			}
			return result;
		}
	}