using System;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;

using UnityEngine;

internal static class dfTexturePacker
{

public static Rect[] PackTextures2( this Texture2D texture, Texture2D[] textures, int padding, int maximumAtlasSize )
{
return PackTextures( texture, textures, padding, 256, 128, maximumAtlasSize );
}

private static Rect[] PackTextures( Texture2D texture, Texture2D[] sprites, int padding, int width, int height, int maxSize )
{

if( ( width > maxSize && height < maxSize ) || ( height > maxSize && width < maxSize ) )
{
width = height = maxSize;
}

if( width > maxSize || height > maxSize )
throw new InvalidOperationException( "Packed sprites exceed maximum atlas size" );

if( height > width )
{
int temp = width;
width = height;
height = temp;
}

MaxRectsBinPack bp = new MaxRectsBinPack( width, height, false );
Rect[] rects = new Rect[ sprites.Length ];

for( int i = 0; i < sprites.Length; i++ )
{

Texture2D sprite = sprites[ i ];
var spriteWidth = sprite.width + padding;
var spriteHeight = sprite.height + padding;

Rect rect = bp.Insert(
spriteWidth,
spriteHeight,
MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestAreaFit
);

// If the rect could not be packed into the current dimensions,
// increase the texture size.
if( rect.width == 0 || rect.height == 0 )
{
return PackTextures( texture, sprites, padding, ( width <= height ? width << 1 : width ), ( height < width ? height << 1 : height ), maxSize );
}

rects[ i ] = rect;

}

// Check for max size
if( width > maxSize || height > maxSize )
throw new InvalidOperationException( "Packed sprites exceed maximum atlas size" );

texture.Resize( width, height );
texture.SetPixels( new Color[ width * height ] );

for( int i = 0; i < sprites.Length; i++ )
{

Texture2D sprite = sprites[ i ];
Rect rect = rects[ i ];
Color[] colors = sprite.GetPixels();

texture.SetPixels(
(int)rect.x,
(int)rect.y,
(int)sprite.width,
(int)sprite.height,
colors
);

rects[ i ] = new Rect(
rect.x / width,
rect.y / height,
( rect.width - padding ) / width,
( rect.height - padding ) / height
);

}

return rects;

}

private class MaxRectsBinPack
{

/*
Based on the Public Domain MaxRectsBinPack.cpp source by Jukka Jylänki
https://github.com/juj/RectangleBinPack/

Ported to C# by Sven Magnus
http://wiki.unity3d.com/index.php/MaxRectsBinPack
This version is also public domain - do whatever you want with it.
*/

public int binWidth = 0;
public int binHeight = 0;
public bool allowRotations;

public List<Rect> usedRectangles = new List<Rect>();
public List<Rect> freeRectangles = new List<Rect>();

public enum FreeRectChoiceHeuristic
{
RectBestShortSideFit, ///< -BSSF: Positions the rectangle against the short side of a free rectangle into which it fits the best.
RectBestLongSideFit, ///< -BLSF: Positions the rectangle against the long side of a free rectangle into which it fits the best.
RectBestAreaFit, ///< -BAF: Positions the rectangle into the smallest free rect into which it fits.
RectBottomLeftRule, ///< -BL: Does the Tetris placement.
RectContactPointRule ///< -CP: Choosest the placement where the rectangle touches other rects as much as possible.
};

public MaxRectsBinPack( int width, int height, bool rotations = false )
{
Init( width, height, rotations );
}

public void Init( int width, int height, bool rotations = false )
{
binWidth = width;
binHeight = height;
allowRotations = rotations;

Rect n = new Rect();
n.x = 0;
n.y = 0;
n.width = width;
n.height = height;

usedRectangles.Clear();

freeRectangles.Clear();
freeRectangles.Add( n );
}

public Rect Insert( int width, int height, FreeRectChoiceHeuristic method )
{
Rect newNode = new Rect();
int score1 = 0; // Unused in this function. We don't need to know the score after finding the position.
int score2 = 0;
switch( method )
{
case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit( width, height, ref score1, ref score2 ); break;
case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft( width, height, ref score1, ref score2 ); break;
case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint( width, height, ref score1 ); break;
case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit( width, height, ref score2, ref score1 ); break;
case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit( width, height, ref score1, ref score2 ); break;
}

if( newNode.height == 0 )
return newNode;

int numRectanglesToProcess = freeRectangles.Count;
for( int i = 0; i < numRectanglesToProcess; ++i )
{
if( SplitFreeNode( freeRectangles[ i ], ref newNode ) )
{
freeRectangles.RemoveAt( i );
--i;
--numRectanglesToProcess;
}
}

PruneFreeList();

usedRectangles.Add( newNode );
return newNode;
}

public void Insert( List<Rect> rects, List<Rect> dst, FreeRectChoiceHeuristic method )
{
dst.Clear();

while( rects.Count > 0 )
{
int bestScore1 = int.MaxValue;
int bestScore2 = int.MaxValue;
int bestRectIndex = -1;
Rect bestNode = new Rect();

for( int i = 0; i < rects.Count; ++i )
{
int score1 = 0;
int score2 = 0;
Rect newNode = ScoreRect( (int)rects[ i ].width, (int)rects[ i ].height, method, ref score1, ref score2 );

if( score1 < bestScore1 || ( score1 == bestScore1 && score2 < bestScore2 ) )
{
bestScore1 = score1;
bestScore2 = score2;
bestNode = newNode;
bestRectIndex = i;
}
}

if( bestRectIndex == -1 )
return;

PlaceRect( bestNode );
rects.RemoveAt( bestRectIndex );
}
}

void PlaceRect( Rect node )
{
int numRectanglesToProcess = freeRectangles.Count;
for( int i = 0; i < numRectanglesToProcess; ++i )
{
if( SplitFreeNode( freeRectangles[ i ], ref node ) )
{
freeRectangles.RemoveAt( i );
--i;
--numRectanglesToProcess;
}
}

PruneFreeList();

usedRectangles.Add( node );
}

Rect ScoreRect( int width, int height, FreeRectChoiceHeuristic method, ref int score1, ref int score2 )
{
Rect newNode = new Rect();
score1 = int.MaxValue;
score2 = int.MaxValue;
switch( method )
{
case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit( width, height, ref score1, ref score2 ); break;
case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft( width, height, ref score1, ref score2 ); break;
case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint( width, height, ref score1 );
score1 = -score1; // Reverse since we are minimizing, but for contact point score bigger is better.
break;
case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit( width, height, ref score2, ref score1 ); break;
case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit( width, height, ref score1, ref score2 ); break;
}

// Cannot fit the current rectangle.
if( newNode.height == 0 )
{
score1 = int.MaxValue;
score2 = int.MaxValue;
}

return newNode;
}

/// Computes the ratio of used surface area.
public float Occupancy()
{
ulong usedSurfaceArea = 0;
for( int i = 0; i < usedRectangles.Count; ++i )
usedSurfaceArea += (uint)usedRectangles[ i ].width * (uint)usedRectangles[ i ].height;

return (float)usedSurfaceArea / ( binWidth * binHeight );
}

Rect FindPositionForNewNodeBottomLeft( int width, int height, ref int bestY, ref int bestX )
{
Rect bestNode = new Rect();
//memset(bestNode, 0, sizeof(Rect));

bestY = int.MaxValue;

for( int i = 0; i < freeRectangles.Count; ++i )
{
// Try to place the rectangle in upright (non-flipped) orientation.
if( freeRectangles[ i ].width >= width && freeRectangles[ i ].height >= height )
{
int topSideY = (int)freeRectangles[ i ].y + height;
if( topSideY < bestY || ( topSideY == bestY && freeRectangles[ i ].x < bestX ) )
{
bestNode.x = freeRectangles[ i ].x;
bestNode.y = freeRectangles[ i ].y;
bestNode.width = width;
bestNode.height = height;
bestY = topSideY;
bestX = (int)freeRectangles[ i ].x;
}
}
if( allowRotations && freeRectangles[ i ].width >= height && freeRectangles[ i ].height >= width )
{
int topSideY = (int)freeRectangles[ i ].y + width;
if( topSideY < bestY || ( topSideY == bestY && freeRectangles[ i ].x < bestX ) )
{
bestNode.x = freeRectangles[ i ].x;
bestNode.y = freeRectangles[ i ].y;
bestNode.width = height;
bestNode.height = width;
bestY = topSideY;
bestX = (int)freeRectangles[ i ].x;
}
}
}
return bestNode;
}

Rect FindPositionForNewNodeBestShortSideFit( int width, int height, ref int bestShortSideFit, ref int bestLongSideFit )
{
Rect bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rect));

bestShortSideFit = int.MaxValue;

for( int i = 0; i < freeRectangles.Count; ++i )
{
// Try to place the rectangle in upright (non-flipped) orientation.
if( freeRectangles[ i ].width >= width && freeRectangles[ i ].height >= height )
{
int leftoverHoriz = Mathf.Abs( (int)freeRectangles[ i ].width - width );
int leftoverVert = Mathf.Abs( (int)freeRectangles[ i ].height - height );
int shortSideFit = Mathf.Min( leftoverHoriz, leftoverVert );
int longSideFit = Mathf.Max( leftoverHoriz, leftoverVert );

if( shortSideFit < bestShortSideFit || ( shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit ) )
{
bestNode.x = freeRectangles[ i ].x;
bestNode.y = freeRectangles[ i ].y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit = shortSideFit;
bestLongSideFit = longSideFit;
}
}

if( allowRotations && freeRectangles[ i ].width >= height && freeRectangles[ i ].height >= width )
{
int flippedLeftoverHoriz = Mathf.Abs( (int)freeRectangles[ i ].width - height );
int flippedLeftoverVert = Mathf.Abs( (int)freeRectangles[ i ].height - width );
int flippedShortSideFit = Mathf.Min( flippedLeftoverHoriz, flippedLeftoverVert );
int flippedLongSideFit = Mathf.Max( flippedLeftoverHoriz, flippedLeftoverVert );

if( flippedShortSideFit < bestShortSideFit || ( flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit ) )
{
bestNode.x = freeRectangles[ i ].x;
bestNode.y = freeRectangles[ i ].y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit = flippedShortSideFit;
bestLongSideFit = flippedLongSideFit;
}
}
}
return bestNode;
}

Rect FindPositionForNewNodeBestLongSideFit( int width, int height, ref int bestShortSideFit, ref int bestLongSideFit )
{
Rect bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rect));

bestLongSideFit = int.MaxValue;

for( int i = 0; i < freeRectangles.Count; ++i )
{
// Try to place the rectangle in upright (non-flipped) orientation.
if( freeRectangles[ i ].width >= width && freeRectangles[ i ].height >= height )
{
int leftoverHoriz = Mathf.Abs( (int)freeRectangles[ i ].width - width );
int leftoverVert = Mathf.Abs( (int)freeRectangles[ i ].height - height );
int shortSideFit = Mathf.Min( leftoverHoriz, leftoverVert );
int longSideFit = Mathf.Max( leftoverHoriz, leftoverVert );

if( longSideFit < bestLongSideFit || ( longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit ) )
{
bestNode.x = freeRectangles[ i ].x;
bestNode.y = freeRectangles[ i ].y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit = shortSideFit;
bestLongSideFit = longSideFit;
}
}

if( allowRotations && freeRectangles[ i ].width >= height && freeRectangles[ i ].height >= width )
{
int leftoverHoriz = Mathf.Abs( (int)freeRectangles[ i ].width - height );
int leftoverVert = Mathf.Abs( (int)freeRectangles[ i ].height - width );
int shortSideFit = Mathf.Min( leftoverHoriz, leftoverVert );
int longSideFit = Mathf.Max( leftoverHoriz, leftoverVert );

if( longSideFit < bestLongSideFit || ( longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit ) )
{
bestNode.x = freeRectangles[ i ].x;
bestNode.y = freeRectangles[ i ].y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit = shortSideFit;
bestLongSideFit = longSideFit;
}
}
}
return bestNode;
}

Rect FindPositionForNewNodeBestAreaFit( int width, int height, ref int bestAreaFit, ref int bestShortSideFit )
{
Rect bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rect));

bestAreaFit = int.MaxValue;

for( int i = 0; i < freeRectangles.Count; ++i )
{
int areaFit = (int)freeRectangles[ i ].width * (int)freeRectangles[ i ].height - width * height;

// Try to place the rectangle in upright (non-flipped) orientation.
if( freeRectangles[ i ].width >= width && freeRectangles[ i ].height >= height )
{
int leftoverHoriz = Mathf.Abs( (int)freeRectangles[ i ].width - width );
int leftoverVert = Mathf.Abs( (int)freeRectangles[ i ].height - height );
int shortSideFit = Mathf.Min( leftoverHoriz, leftoverVert );

if( areaFit < bestAreaFit || ( areaFit == bestAreaFit && shortSideFit < bestShortSideFit ) )
{
bestNode.x = freeRectangles[ i ].x;
bestNode.y = freeRectangles[ i ].y;
bestNode.width = width;
bestNode.height = height;
bestShortSideFit = shortSideFit;
bestAreaFit = areaFit;
}
}

if( allowRotations && freeRectangles[ i ].width >= height && freeRectangles[ i ].height >= width )
{
int leftoverHoriz = Mathf.Abs( (int)freeRectangles[ i ].width - height );
int leftoverVert = Mathf.Abs( (int)freeRectangles[ i ].height - width );
int shortSideFit = Mathf.Min( leftoverHoriz, leftoverVert );

if( areaFit < bestAreaFit || ( areaFit == bestAreaFit && shortSideFit < bestShortSideFit ) )
{
bestNode.x = freeRectangles[ i ].x;
bestNode.y = freeRectangles[ i ].y;
bestNode.width = height;
bestNode.height = width;
bestShortSideFit = shortSideFit;
bestAreaFit = areaFit;
}
}
}
return bestNode;
}

/// Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap otherwise.
int CommonIntervalLength( int i1start, int i1end, int i2start, int i2end )
{
if( i1end < i2start || i2end < i1start )
return 0;
return Mathf.Min( i1end, i2end ) - Mathf.Max( i1start, i2start );
}

int ContactPointScoreNode( int x, int y, int width, int height )
{
int score = 0;

if( x == 0 || x + width == binWidth )
score += height;
if( y == 0 || y + height == binHeight )
score += width;

for( int i = 0; i < usedRectangles.Count; ++i )
{
if( usedRectangles[ i ].x == x + width || usedRectangles[ i ].x + usedRectangles[ i ].width == x )
score += CommonIntervalLength( (int)usedRectangles[ i ].y, (int)usedRectangles[ i ].y + (int)usedRectangles[ i ].height, y, y + height );
if( usedRectangles[ i ].y == y + height || usedRectangles[ i ].y + usedRectangles[ i ].height == y )
score += CommonIntervalLength( (int)usedRectangles[ i ].x, (int)usedRectangles[ i ].x + (int)usedRectangles[ i ].width, x, x + width );
}
return score;
}

Rect FindPositionForNewNodeContactPoint( int width, int height, ref int bestContactScore )
{
Rect bestNode = new Rect();
//memset(&bestNode, 0, sizeof(Rect));

bestContactScore = -1;

for( int i = 0; i < freeRectangles.Count; ++i )
{
// Try to place the rectangle in upright (non-flipped) orientation.
if( freeRectangles[ i ].width >= width && freeRectangles[ i ].height >= height )
{
int score = ContactPointScoreNode( (int)freeRectangles[ i ].x, (int)freeRectangles[ i ].y, width, height );
if( score > bestContactScore )
{
bestNode.x = (int)freeRectangles[ i ].x;
bestNode.y = (int)freeRectangles[ i ].y;
bestNode.width = width;
bestNode.height = height;
bestContactScore = score;
}
}
if( allowRotations && freeRectangles[ i ].width >= height && freeRectangles[ i ].height >= width )
{
int score = ContactPointScoreNode( (int)freeRectangles[ i ].x, (int)freeRectangles[ i ].y, height, width );
if( score > bestContactScore )
{
bestNode.x = (int)freeRectangles[ i ].x;
bestNode.y = (int)freeRectangles[ i ].y;
bestNode.width = height;
bestNode.height = width;
bestContactScore = score;
}
}
}
return bestNode;
}

bool SplitFreeNode( Rect freeNode, ref Rect usedNode )
{
// Test with SAT if the rectangles even intersect.
if( usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x ||
usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y )
return false;

if( usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x )
{
// New node at the top side of the used node.
if( usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height )
{
Rect newNode = freeNode;
newNode.height = usedNode.y - newNode.y;
freeRectangles.Add( newNode );
}

// New node at the bottom side of the used node.
if( usedNode.y + usedNode.height < freeNode.y + freeNode.height )
{
Rect newNode = freeNode;
newNode.y = usedNode.y + usedNode.height;
newNode.height = freeNode.y + freeNode.height - ( usedNode.y + usedNode.height );
freeRectangles.Add( newNode );
}
}

if( usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y )
{
// New node at the left side of the used node.
if( usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width )
{
Rect newNode = freeNode;
newNode.width = usedNode.x - newNode.x;
freeRectangles.Add( newNode );
}

// New node at the right side of the used node.
if( usedNode.x + usedNode.width < freeNode.x + freeNode.width )
{
Rect newNode = freeNode;
newNode.x = usedNode.x + usedNode.width;
newNode.width = freeNode.x + freeNode.width - ( usedNode.x + usedNode.width );
freeRectangles.Add( newNode );
}
}

return true;
}

void PruneFreeList()
{
for( int i = 0; i < freeRectangles.Count; ++i )
for( int j = i + 1; j < freeRectangles.Count; ++j )
{
if( IsContainedIn( freeRectangles[ i ], freeRectangles[ j ] ) )
{
freeRectangles.RemoveAt( i );
--i;
break;
}
if( IsContainedIn( freeRectangles[ j ], freeRectangles[ i ] ) )
{
freeRectangles.RemoveAt( j );
--j;
}
}
}

bool IsContainedIn( Rect a, Rect b )
{
return a.x >= b.x && a.y >= b.y
&& a.x + a.width <= b.x + b.width
&& a.y + a.height <= b.y + b.height;
}

}

}

unity3d平铺图片的更多相关文章

  1. [ATL/WTL]_[0基础]_[CBitmap复制图片-截取图片-平铺图片]

    场景: 1.当你须要截取图片部分区域作为某个控件的背景. 2.须要平铺图片到一个大区域让他自己主动放大时. 3.或者须要合并图片时. 代码: CDC sdc; CDC ddc; sdc.CreateC ...

  2. Java 设置PDF平铺图片背景(水印)

    一.概述及环境准备 本文介绍使用免费版PDF库-Free Spire.PDF for Java加载图片来设置成PDF平铺图片背景的效果,也可以作为平铺图片水印来使用:编辑代码前,需要先导入jar文件, ...

  3. Android中设定背景图片平铺。

    注:本文由Colin撰写,版权所有!转载请注明原文地址,谢谢合作! 在做Android开发时,我们常常需要为程序设定一个背景,但由于现在的Android设备尺寸不一,如果随便设置一个图片为背景,那么很 ...

  4. UIImage图片处理,旋转、截取、平铺、缩放等操作

    来源:iOS_小松哥 链接:http://www.jianshu.com/p/9ab1205f5166 有时候我们需要处理图片,比如改变大小,旋转,截取等等,所以今天说一说图片处理相关的一些操作. 本 ...

  5. iOS UIButton 设置图片平铺

    UIImage *image2 = [UIImage imageNamed:imgName]; CGFloat top = ; // 顶端盖高度 CGFloat bottom = ; // 底端盖高度 ...

  6. ie8下背景图片平铺问题

    IE9+及其他浏览器实现背景图片平铺可能需要一个属性就可以background-size:100%/cover; 但是ie8下background-size是不兼容的,因此我们需要用到滤镜,来解决背景 ...

  7. Duilib技巧:背景图片平铺

    贴图的描述 方式有两种    // 1.aaa.jpg    // 2.file='aaa.jpg' res='' restype='0' dest='0,0,0,0' source='0,0,0,0 ...

  8. Android 背景图片重复平铺

    有时候我们需要将一个图片横向或者纵向的平铺(重复循环),这个时候我们需要创建一个xml文件,如下: <?xml version ="1.0" encoding =" ...

  9. 一款js控制背景图片平铺

    背景图片的平铺方法有很多种,纯色背景,渐变背景,图片背景,今天讲的是移动端的图片背景~~~~ <style> html,body{;;} .body{background: url(ima ...

随机推荐

  1. EPOCH batchsize

    只有在数据很庞大的时候(在机器学习中,几乎任何时候都是),我们才需要使用 epochs,batch size,迭代这些术语,在这种情况下,一次性将数据输入计算机是不可能的.因此,为了解决这个问题,我们 ...

  2. Python Redis hash

    hash表现形式上有些像pyhton中的dict,可以存储一组关联性较强的数据 , redis中Hash在内存中的存储格式如下图: hset(name, key, value) # name对应的ha ...

  3. Java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed

    在使用response重定向的时候,报以下错误:Java.lang.IllegalStateException: Cannot call sendRedirect() after the respon ...

  4. 王之泰201771010131《面向对象程序设计(java)》第七周学习总结

    王之泰201771010131<面向对象程序设计(java)>第七周学习总结 第一部分:理论知识学习部分 第五章 第五章内容深度学习: 继承:如果两个类存在继承关系,则子类会自动继承父类的 ...

  5. MySQL 实战笔记

    01 | 基础架构:一条SQL查询语句是如何执行的? 大体可以分为: Server 层:包含了连接器.查询缓存.分析器.优化器.执行器,跨存储引擎的功能都在这一层实现的,比如存储过程.触发器.视图等. ...

  6. Docker镜像Push到DockerHub

    1.自己整理的容器首先通过commit做成本地镜像 docker commit -a "Cristin" -m "测试开发平台Jenkins" 281eef85 ...

  7. 虚拟现实外包公司—VR游戏你不知道的以及你该知道的WebVR

    VR基础——原理其实很简单 我们地球人之所以能够看到立体的景物,是因为双眼可以各自独立看东西,也就是左眼只能看到左眼的景物,而右眼只能看到右眼的景物.因为人类左右两眼有间距,造成两眼的视角有些细微的差 ...

  8. CentOS7.x系统中使用Docker时,在存储方面需要注意的问题

    简述: 1.Docker 1.12.6/v17.03文档中CentOS7系统下安装时,明确说明,用于生产时,必须使用devicemapper驱动的direct-lvm模式,需要我们提前准备好块设备,以 ...

  9. js获取css样式封装

    封装 function getStyle(obj , attr){ return obj.currentStyle?obj.currentStyle[attr]:getComputedStyle(ob ...

  10. 整合MyBatis(springboot)

    pom文件: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>m ...