private static AtlasPackingResult MergeAtlasPackingResultStackBonA(AtlasPackingResult a, AtlasPackingResult b, int maxWidthDim, int maxHeightDim, bool stretchBToAtlasWidth, IPipeline pipeline) { Debug.Assert(a.usedW == a.atlasX); Debug.Assert(a.usedH == a.atlasY); Debug.Assert(b.usedW == b.atlasX); Debug.Assert(b.usedH == b.atlasY); Debug.Assert(a.usedW <= maxWidthDim, a.usedW + " " + maxWidthDim); Debug.Assert(a.usedH <= maxHeightDim, a.usedH + " " + maxHeightDim); Debug.Assert(b.usedH <= maxHeightDim); Debug.Assert(b.usedW <= maxWidthDim, b.usedW + " " + maxWidthDim); Rect AatlasToFinal; Rect BatlasToFinal; // first calc height scale and offset int atlasX; int atlasY; pipeline.MergeAtlasPackingResultStackBonAInternal(a, b, out AatlasToFinal, out BatlasToFinal, stretchBToAtlasWidth, maxWidthDim, maxHeightDim, out atlasX, out atlasY); Rect[] newRects = new Rect[a.rects.Length + b.rects.Length]; AtlasPadding[] paddings = new AtlasPadding[a.rects.Length + b.rects.Length]; int[] srcImgIdxs = new int[a.rects.Length + b.rects.Length]; Array.Copy(a.padding, paddings, a.padding.Length); Array.Copy(b.padding, 0, paddings, a.padding.Length, b.padding.Length); Array.Copy(a.srcImgIdxs, srcImgIdxs, a.srcImgIdxs.Length); Array.Copy(b.srcImgIdxs, 0, srcImgIdxs, a.srcImgIdxs.Length, b.srcImgIdxs.Length); Array.Copy(a.rects, newRects, a.rects.Length); for (int i = 0; i < a.rects.Length; i++) { Rect r = a.rects[i]; r.x = AatlasToFinal.x + r.x * AatlasToFinal.width; r.y = AatlasToFinal.y + r.y * AatlasToFinal.height; r.width *= AatlasToFinal.width; r.height *= AatlasToFinal.height; Debug.Assert(r.max.x <= 1f); Debug.Assert(r.max.y <= 1f); Debug.Assert(r.min.x >= 0f); Debug.Assert(r.min.y >= 0f); newRects[i] = r; srcImgIdxs[i] = a.srcImgIdxs[i]; } for (int i = 0; i < b.rects.Length; i++) { Rect r = b.rects[i]; r.x = BatlasToFinal.x + r.x * BatlasToFinal.width; r.y = BatlasToFinal.y + r.y * BatlasToFinal.height; r.width *= BatlasToFinal.width; r.height *= BatlasToFinal.height; Debug.Assert(r.max.x <= 1f); Debug.Assert(r.max.y <= 1f); Debug.Assert(r.min.x >= 0f); Debug.Assert(r.min.y >= 0f); newRects[a.rects.Length + i] = r; srcImgIdxs[a.rects.Length + i] = b.srcImgIdxs[i]; } AtlasPackingResult res = new AtlasPackingResult(paddings); res.atlasX = atlasX; res.atlasY = atlasY; res.padding = paddings; res.rects = newRects; res.srcImgIdxs = srcImgIdxs; res.CalcUsedWidthAndHeight(); return(res); }
//----------------- Algorithm for fitting everything into multiple Atlases // // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size. // Sort images from big to small using either height, width or area comparer // // If an image is bigger than maxDim, then shrink it to max size on the largest dimension // distribute images using the new algorithm, should never have to expand the atlas instead create new atlases as needed // should not need to scale atlases // AtlasPackingResult[] _GetRectsMultiAtlas(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionPassedX, int maxDimensionPassedY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimensionX={1}, maxDimensionY={2} minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}", imgWidthHeights.Count, maxDimensionPassedX, maxDimensionPassedY, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY)); float area = 0; int maxW = 0; int maxH = 0; ImageAreaInAtlas[] imgsToAdd = new ImageAreaInAtlas[imgWidthHeights.Count]; int maxDimensionX = maxDimensionPassedX; int maxDimensionY = maxDimensionPassedY; if (atlasMustBePowerOfTwo) { maxDimensionX = RoundToNearestPositivePowerOfTwo(maxDimensionX); maxDimensionY = RoundToNearestPositivePowerOfTwo(maxDimensionY); } for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; //shrink the image so that it fits in maxDimenion if it is larger than maxDimension if atlas exceeds maxDim x maxDim then new alas will be created iw = Mathf.Min(iw, maxDimensionX - paddings[i].leftRight * 2); ih = Mathf.Min(ih, maxDimensionY - paddings[i].topBottom * 2); ImageAreaInAtlas im = imgsToAdd[i] = new ImageAreaInAtlas(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } //explore the space to find a resonably efficient packing //int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (atlasMustBePowerOfTwo) { idealAtlasH = RoundToNearestPositivePowerOfTwo(maxDimensionY); idealAtlasW = RoundToNearestPositivePowerOfTwo(maxDimensionX); } else { idealAtlasH = maxDimensionY; idealAtlasW = maxDimensionX; } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } ProbeResult pr = new ProbeResult(); Array.Sort(imgsToAdd, new ImageHeightComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { bestRoot = pr; } Array.Sort(imgsToAdd, new ImageWidthComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { if (pr.totalAtlasArea < bestRoot.totalAtlasArea) { bestRoot = pr; } } Array.Sort(imgsToAdd, new ImageAreaComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { if (pr.totalAtlasArea < bestRoot.totalAtlasArea) { bestRoot = pr; } } if (bestRoot == null) { return(null); } Debug.Log("Best fit found: w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <AtlasPackingResult> rs = new List <AtlasPackingResult>(); // find all Nodes that are an individual atlas List <AtalsAreaNode> atlasNodes = new List <AtalsAreaNode>(); Stack <AtalsAreaNode> stack = new Stack <AtalsAreaNode>(); AtalsAreaNode node = bestRoot.root; while (node != null) { stack.Push(node); node = node.child[0]; } // traverse the tree collecting atlasNodes while (stack.Count > 0) { node = stack.Pop(); if (node.isFullAtlas == NodeType.maxDim) { atlasNodes.Add(node); } if (node.child[1] != null) { node = node.child[1]; while (node != null) { stack.Push(node); node = node.child[0]; } } } //pack atlases so they all fit for (int i = 0; i < atlasNodes.Count; i++) { List <ImageAreaInAtlas> images = new List <ImageAreaInAtlas>(); flattenTree(atlasNodes[i], images); Rect[] rss = new Rect[images.Count]; int[] srcImgIdx = new int[images.Count]; for (int j = 0; j < images.Count; j++) { rss[j] = (new Rect(images[j].x - atlasNodes[i].nodeAreaRect.x, images[j].y, images[j].w, images[j].h)); srcImgIdx[j] = images[j].imgId; } AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); GetExtent(atlasNodes[i], ref res.usedW, ref res.usedH); res.usedW -= atlasNodes[i].nodeAreaRect.x; int outW = atlasNodes[i].nodeAreaRect.w; int outH = atlasNodes[i].nodeAreaRect.h; if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(res.usedW), atlasNodes[i].nodeAreaRect.w); outH = Mathf.Min(CeilToNearestPowerOfTwo(res.usedH), atlasNodes[i].nodeAreaRect.h); if (outH < outW / 2) { outH = outW / 2; //smaller dim can't be less than half larger } if (outW < outH / 2) { outW = outH / 2; } } else { outW = res.usedW; outH = res.usedH; } res.atlasY = outH; res.atlasX = outW; res.rects = rss; res.srcImgIdxs = srcImgIdx; res.CalcUsedWidthAndHeight(); rs.Add(res); normalizeRects(res, paddings[i]); Debug.Log(String.Format("Done GetRects ")); } return(rs.ToArray()); }
AtlasPackingResult _GetRectsSingleAtlas(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) { AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); List <Rect> rects = new List <Rect>(); int extent = 0; int maxh = 0; int maxw = 0; List <ImageAreaInAtlas> images = new List <ImageAreaInAtlas>(); Debug.Log("Packing rects for: " + imgWidthHeights.Count); for (int i = 0; i < imgWidthHeights.Count; i++) { ImageAreaInAtlas im = new ImageAreaInAtlas(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, paddings[i], minImageSizeX, minImageSizeY); // if images are stacked horizontally then there is no padding at the top or bottom if (packingOrientation == TexturePackingOrientation.vertical) { im.h -= paddings[i].topBottom * 2; im.x = extent; im.y = 0; rects.Add(new Rect(im.w, im.h, extent, 0)); extent += im.w; maxh = Mathf.Max(maxh, im.h); } else { im.w -= paddings[i].leftRight * 2; im.y = extent; im.x = 0; rects.Add(new Rect(im.w, im.h, 0, extent)); extent += im.h; maxw = Mathf.Max(maxw, im.w); } images.Add(im); } //scale atlas to fit maxDimension Vector2 rootWH; if (packingOrientation == TexturePackingOrientation.vertical) { rootWH = new Vector2(extent, maxh); } else { rootWH = new Vector2(maxw, extent); } int outW = (int)rootWH.x; int outH = (int)rootWH.y; if (packingOrientation == TexturePackingOrientation.vertical) { if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(outW), maxDimensionX); } else { outW = Mathf.Min(outW, maxDimensionX); } } else { if (atlasMustBePowerOfTwo) { outH = Mathf.Min(CeilToNearestPowerOfTwo(outH), maxDimensionY); } else { outH = Mathf.Min(outH, maxDimensionY); } } float padX, padY; int newMinSizeX, newMinSizeY; if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimensionX, maxDimensionY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY)) { res = new AtlasPackingResult(paddings.ToArray()); res.rects = new Rect[images.Count]; res.srcImgIdxs = new int[images.Count]; res.atlasX = outW; res.atlasY = outH; for (int i = 0; i < images.Count; i++) { ImageAreaInAtlas im = images[i]; Rect r; if (packingOrientation == TexturePackingOrientation.vertical) { r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH, (float)im.w / (float)outW - padX * 2f, stretchImagesToEdges ? 1f : (float)im.h / (float)outH); // all images are stretched to fill the height } else { r = res.rects[i] = new Rect((float)im.x / (float)outW, (float)im.y / (float)outH + padY, (stretchImagesToEdges ? 1f : ((float)im.w / (float)outW)), (float)im.h / (float)outH - padY * 2f); // all images are stretched to fill the height } res.srcImgIdxs[i] = im.imgId; Debug.Log("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + paddings[i] + " outW=" + outW + " outH=" + outH); } res.CalcUsedWidthAndHeight(); return(res); } Debug.Log("Packing failed returning null atlas result"); return(null); }
//------------------ Algorithm for fitting everything into one atlas and scaling down // // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size. // Sort images from big to small using either height, width or area comparer // Explore space to find a resonably efficient packing. Grow the atlas gradually until a fit is found // Scale atlas to fit // AtlasPackingResult _GetRectsSingleAtlas(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) { Debug.Log(string.Format("_GetRects 图片数量 ={0}, 最大尺寸X ={1}, 最小尺寸 X={2}, 最小尺寸 Y ={3}, masterImageSizeX={4}, masterImageSizeY={5}, 递归深度 = {6}", imgWidthHeights.Count, maxDimensionX, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); if (recursionDepth > 10) { //最大递归深度设定为 10 Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures."); return(null); } float allImageTotalArea = 0; int maxW = 0; int maxH = 0; ImageAreaInAtlas[] imgsToAdd = new ImageAreaInAtlas[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; ImageAreaInAtlas im = imgsToAdd[i] = new ImageAreaInAtlas(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); allImageTotalArea += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing //探索图片空间以找到合理有效的包装 int sqrtArea = (int)Mathf.Sqrt(allImageTotalArea); int idealAtlasW; int idealAtlasH; if (atlasMustBePowerOfTwo) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(allImageTotalArea / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(allImageTotalArea / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); if (ProbeSingleAtlas(imgsToAdd, steppedWidth, steppedHeight, allImageTotalArea, maxDimensionX, maxDimensionY, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(atlasMustBePowerOfTwo) > bestRoot.GetScore(atlasMustBePowerOfTwo)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = SetStepWidthHeight(steppedWidth, stepW, maxDimensionX); Debug.Log("增加 Width h=" + steppedHeight + " w=" + steppedWidth); } } steppedHeight = SetStepWidthHeight(steppedHeight, stepH, maxDimensionY); Debug.Log("增加 Height h=" + steppedHeight + " w=" + steppedWidth); } if (bestRoot == null) { return(null); } int outW = 0; int outH = 0; if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimensionX); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimensionY); if (outH < outW / 2) { outH = outW / 2; //smaller dim can't be less than half larger } if (outW < outH / 2) { outW = outH / 2; } } else { outW = Mathf.Min(bestRoot.w, maxDimensionX); outH = Mathf.Min(bestRoot.h, maxDimensionY); } bestRoot.outW = outW; bestRoot.outH = outH; Debug.Log("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); //Debug.Assert(images.Count != imgsToAdd.Length, "Result images not the same lentgh as source")); //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <ImageAreaInAtlas> images = new List <ImageAreaInAtlas>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit Vector2 rootWH = new Vector2(bestRoot.w, bestRoot.h); float padX, padY; int newMinSizeX, newMinSizeY; if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimensionX, maxDimensionY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY)) { AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); res.rects = new Rect[images.Count]; res.srcImgIdxs = new int[images.Count]; res.atlasX = outW; res.atlasY = outH; res.usedW = -1; res.usedH = -1; for (int i = 0; i < images.Count; i++) { ImageAreaInAtlas im = images[i]; Rect r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2f, (float)im.h / (float)outH - padY * 2f); res.srcImgIdxs[i] = im.imgId; Debug.Log("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + paddings[i]); } res.CalcUsedWidthAndHeight(); return(res); } else { Debug.Log("==================== REDOING PACKING ================"); //root = null; return(_GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, recursionDepth + 1)); } //Debug.Log(String.Format("Done GetRects atlasW={0} atlasH={1}", bestRoot.w, bestRoot.h)); //return res; }
AtlasPackingResult[] _GetRectsMultiAtlasVertical(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionPassedX, int maxDimensionPassedY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY) { List <AtlasPackingResult> rs = new List <AtlasPackingResult>(); int extent = 0; int maxh = 0; int maxw = 0; Debug.Log("Packing rects for: " + imgWidthHeights.Count); List <ImageAreaInAtlas> allImages = new List <ImageAreaInAtlas>(); for (int i = 0; i < imgWidthHeights.Count; i++) { ImageAreaInAtlas im = new ImageAreaInAtlas(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, paddings[i], minImageSizeX, minImageSizeY); im.h -= paddings[i].topBottom * 2; allImages.Add(im); } allImages.Sort(new ImageWidthComparer()); List <ImageAreaInAtlas> images = new List <ImageAreaInAtlas>(); List <Rect> rects = new List <Rect>(); int spaceRemaining = maxDimensionPassedX; while (allImages.Count > 0 || images.Count > 0) { ImageAreaInAtlas im = PopLargestThatFits(allImages, spaceRemaining, maxDimensionPassedX, images.Count == 0); if (im == null) { Debug.Log("Atlas filled creating a new atlas "); AtlasPackingResult apr = new AtlasPackingResult(paddings.ToArray()); apr.atlasX = maxw; apr.atlasY = maxh; Rect[] rss = new Rect[images.Count]; int[] srcImgIdx = new int[images.Count]; for (int j = 0; j < images.Count; j++) { Rect r = new Rect(images[j].x, images[j].y, images[j].w, stretchImagesToEdges ? maxh : images[j].h); rss[j] = r; srcImgIdx[j] = images[j].imgId; } apr.rects = rss; apr.srcImgIdxs = srcImgIdx; apr.CalcUsedWidthAndHeight(); images.Clear(); rects.Clear(); extent = 0; maxh = 0; rs.Add(apr); spaceRemaining = maxDimensionPassedX; } else { im.x = extent; im.y = 0; images.Add(im); rects.Add(new Rect(extent, 0, im.w, im.h)); extent += im.w; maxh = Mathf.Max(maxh, im.h); maxw = extent; spaceRemaining = maxDimensionPassedX - extent; } } for (int i = 0; i < rs.Count; i++) { int outW = rs[i].atlasX; int outH = Mathf.Min(rs[i].atlasY, maxDimensionPassedY); if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(outW), maxDimensionPassedX); } else { outW = Mathf.Min(outW, maxDimensionPassedX); } rs[i].atlasX = outW; //------------------------------- //scale atlas to fit maxDimension float padX, padY; int newMinSizeX, newMinSizeY; ScaleAtlasToFitMaxDim(new Vector2(rs[i].atlasX, rs[i].atlasY), images, maxDimensionPassedX, maxDimensionPassedY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY); } //normalize atlases so that that rects are 0 to 1 for (int i = 0; i < rs.Count; i++) { normalizeRects(rs[i], paddings[i]); rs[i].CalcUsedWidthAndHeight(); } //----------------------------- return(rs.ToArray()); }