Android 自定義View學(xué)習(xí)(八)——Matrix知識學(xué)習(xí)

Matrix主要用于對圖像的圖形處理痕惋。前面學(xué)習(xí)的ColorMatirx主要是圖像色彩的處理

學(xué)習(xí)資料

十分感謝 : )


1.Martrix 變形矩陣

Matrix是一個3 * 3的矩陣钻蹬,每個像素點表達了其坐標的X,Y信息

圖形矩陣變換

處理每個像素點的計算方法

X1 = a * X + b * Y + c
Y1 = d * X + e * Y + f
L  = g * X + h * Y + i

一般葫笼,g = h = 0 , i = 1祝闻,這時L = g * X + h * Y + i 恒成立,也就是L = i = 1

Matrix的初始化矩陣,對角線為1,其余為0

Matrix初始化矩陣

Matrix主要可以對圖像做4種基本變換

  • Translate 平移變換
  • Rotate 旋轉(zhuǎn)變換
  • Scale 縮放變換
  • Skew 錯切變換

Matrix類中的方法豆瘫,主要也是和這四個變換相關(guān),只是對計算過程做了封裝

作用對象是Bitmap而不是Canvas


2. Translate 平移變換

平移變換

紅點p1平移到白點p時左痢,坐標值

x = x1 + x0
y = y1 + y0

矩陣的形式:

平移變換矩陣

為了更好直觀表現(xiàn)靡羡,先看原始效果

原始效果
private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    //畫筆
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.parseColor("#FF4081"));
    //矩陣
    matrix = new Matrix();
    matrix.setTranslate(100f,100f);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    canvas.drawBitmap(bitmap,0,0,null);  
}

在布局文件中系洛,控件的寬為match_parent俊性,高為200dponDraw()方法中描扯,canvas繪制底色為黃色定页,又繪制了原始了的bitmapbitmap的大小是沒有控件大的绽诚,屏幕右側(cè)留下了一塊黃色的區(qū)域典徊。此時并沒有用到matrix


2.1 setTranslate()方法

Matrix中提供了一個setTranslate()方法,很容易就做到平移

簡單修改代碼

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    canvas.drawBitmap(bitmap,matrix,null);
    //在(100恩够,100)處畫一個圓  
    //用來輔助查看matrix作用后的坐標系
    canvas.drawCircle(100,100,30,paint);
}
平移后

根據(jù)小圓可以看出卒落,matrix的平移對canvas的坐標系不會造成影響,不像canvas.traslate()方法蜂桶。

matrix.setTranslate(100f,100f)儡毕,bitmapx,y軸上移動了100px

代入到平移的公式中:

平移100個像素

最終

x = x1 + 100
y = y1 + 100

而超出了canvas的部分扑媚,則不再顯示


3. Rotate 旋轉(zhuǎn)變換

旋轉(zhuǎn)就是一個點圍繞一個中心點旋轉(zhuǎn)到新的位置

以原點的為旋轉(zhuǎn)中心過程學(xué)習(xí):

Rotate旋轉(zhuǎn)圖示

白點p(x0,yo)繞原點旋轉(zhuǎn)β°后腰湾,得到紅點p(x,y)

斜邊為r,角度為α疆股,利用三角函數(shù)费坊,得到

x0 = r * cosα
y0 = r * sinα

同理,可以得出

x = r * cos(α + β) = r * cosα * cosβ - r * sinα * sinβ = x0 * cosβ - y0 * sinβ

y = r * sin(α + β) = r * sinα * cosβ + r * cosα * sinβ =  y0 * cosβ + x0 * sinβ

過程其實就是三角函數(shù)展開旬痹,矩陣的形式就是

旋轉(zhuǎn)矩陣變換

根據(jù)計算結(jié)果y = y0 * cosβ + x0 * sinβ附井,需要注意sinβcosβ在矩陣的位置


上面的情況是以原點為旋轉(zhuǎn)中心,任意點O為旋轉(zhuǎn)中心進行旋轉(zhuǎn)變換两残,一般有3個步驟:

  1. 將坐標原點移動到任意點O
  2. 使用上面的以坐標系原點為中心的旋轉(zhuǎn)方法進行旋轉(zhuǎn)
  3. 將坐標原點還原

主要就是考慮任意點與原點的坐標錯羡忘。然而使用setRotate()方法時,并不用考慮過多磕昼,都進行了封裝


3.1 setRotate()方法

旋轉(zhuǎn)方法有兩個重載方法:

1. setRotate(float degrees)
2. setRotate(float degrees, float px, float py)

第一個方法簡單使用卷雕,簡單修改2.1中的代碼

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setRotate(15);
}
圍繞左上角旋轉(zhuǎn)15度

默認以左上角為旋轉(zhuǎn)中心,bitmap的寬為r進行旋轉(zhuǎn)


第2個方法可以指定旋轉(zhuǎn)中心O票从,float px就是O點的X軸坐標漫雕,float py就是O點的Y軸坐標

matrix.setRotate(15,bitmap.getWidth()/2,bitmap.getHeight()/2);
以Bitmap中心為旋轉(zhuǎn)中心

bitmap中心為旋轉(zhuǎn)中心進行旋轉(zhuǎn)15度


4.Scale 縮放變換

對于一個像素點來說滨嘱,不存在縮放的概念,但一個圖像是由很多個像素點組成浸间,將每個點的坐標進行相同比例的縮放后太雨,整個圖像也就有了縮放的效果。

計算公式:

x = K1 * x0
y = k1 * y0

矩陣形式:

矩陣縮放變換

k1 就是要縮放的比例魁蒜,負值無效囊扳,bitmap會不顯示,0~1f縮小兜看,k1 > 1 為放大


4.1 setScale() 縮放方法

這個方法也有兩個重載方法

1. setScale(float sx, float sy)
2. setScale(float sx, float sy, float px, float py)

根據(jù)學(xué)習(xí)setRotate()方法锥咸,這個方法的兩個重載方法比較好理解


第1個方法,簡單使用

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setScale(0.5f,0.5f);
}
縮放二分之一

此時的縮放中心為bitmap的坐上角


第2個方法细移,簡單使用

matrix.setScale(0.5f,0.5f,bitmap.getWidth()/2,bitmap.getHeight()/2);
以Bitmap中心縮放

此時就是以Bitmap的中心進行縮放搏予,整個Bitmap的邊緣向中間靠攏


5. Skew 錯切變換

錯切變換skew是一種比較特殊的線性變換,分為水平錯切和垂直錯切

5.1 水平錯切

水平錯切效果就是讓所有像素點的Y軸坐標不變弧轧,X軸坐標按照比例進行平移雪侥,且平移的大小與該點到Y軸的距離成成正比

在坐標系中的效果:

水平錯切

計算公式:

x = x0 + k1 * y0
y = y0

矩陣形式:

矩陣水平錯切變換

X軸平移的值,是k1 * y0


5.2 垂直錯切

垂直錯切讓所有像素點的X軸坐標不變精绎,Y軸坐標按照比例進行平移速缨,且平移的大小與該點到X軸的距離成成正比

在坐標系中的效果:

垂直錯切

計算公式:

x = x0 
y = y0+ k2 * x0

矩陣形式:

矩陣垂直錯切變換

5.3 兩個方向都進行錯切

當(dāng)水平和垂直方向上都做錯切變換時

計算公式:

x = x0 + k1 * y0
y = k2 * x0 * y0

矩陣形式:

水平和垂直都錯切變換

無論水平還垂直錯切,最終的效果其實就是由矩形變作平行四邊形


5.3 setSkew() 錯切方法

這個方法也有兩個重載

1. setSkew(float kx, float ky)
2. setSkew(float kx, float ky, float px, float py)

第2個方法后面兩個參數(shù)也是為了指定錯切的中心


5.3.1 setSkew(float kx, float ky)第一個方法

水平錯切:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setSkew(0.25f,0f);
}
水平錯切效果

kx就是k1代乃,負值旬牲,向左切;正值向右切


垂直錯切:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setSkew(0f,0.25f);
}
垂直錯切效果

ky就是k2襟己,負值引谜,向上切;正值擎浴,向下切

bitmap最右邊的區(qū)域不是錯切的效果员咽,是因為bitmap的寬沒有canvas的寬大,留下的空白區(qū)域


兩個方向都錯切:

matrix.setSkew(0.25f,0.25f);
兩個方向錯切

此時可以明顯看出贮预,錯切的中心點為bitmap的左上角


5.3.2 setSkew(float kx, float ky, float px, float py) 指定錯切中心

簡單使用:

matrix.setSkew(0.1f,0.1f,bitmap.getWidth()/2,bitmap.getHeight()/2);
指定錯切中心點

bitmap的中心為錯切中心點

這里目前并不是很理解指定中心點后錯切對坐標系的影響


6.矩陣中的元素與四種變換效果的對應(yīng)關(guān)系

矩陣
  • a和e 控制縮放變換
  • b和d 控制錯切變換
  • c和f 控制平移變換
  • a贝室,b,d仿吞,e 共同控制旋轉(zhuǎn)變換
變化過濾

第1行都是影響的X軸滑频,第2行影響的Y


7.關(guān)于前乘和后乘

首先,矩陣的乘法不滿足乘法的交換規(guī)律

Matrix類中唤冈,set方法會重置矩陣中的所有值峡迷,而prepost不會


7.1 簡單對比

前乘的代碼:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setTranslate(100,100);
    matrix.preRotate(15);
}
前乘效果

先進行平移,前乘旋轉(zhuǎn)15°


后乘,簡單修改代碼:

matrix.postRotate(15);
后乘效果

兩者差別绘搞,看右下角的區(qū)域比較明顯


7.2 嘗試分析

前乘旋轉(zhuǎn)源碼
旋轉(zhuǎn)后乘源碼

前乘就是M * R(degrees)彤避,后乘就是R(degrees) * M
前乘就對應(yīng)線性代數(shù)矩陣運算的右乘
后乘就對應(yīng)線性代數(shù)矩陣運算的左乘

在矩陣運算中:
M右乘A,就是A * M
M左乘A夯辖,就是M * A

簡單記法:右乘從右邊乘進來琉预,左乘從左邊乘進來


7.1中的矩陣:

矩陣分析

7.1中共有3個矩陣,首先平移b蒿褂,旋轉(zhuǎn)a圆米,像素c

在前乘或者后乘之前有一個setTranslate(100,100)設(shè)置的矩陣b,前乘后后乘也就是相對于b來說

7.1前乘啄栓,計算過程就是:
a右乘b娄帖,計算就是b * a,得到一個新的矩陣N谴供,N * c

后乘的過程:
a左乘b块茁,計算就是a * b齿坷,得到一個新的矩陣N桂肌,N * c

總結(jié):
pre或者post方法前進行設(shè)置了哪個矩陣M,矩陣MM之前所有的矩陣的運算得到的新矩陣N永淌,N就看做當(dāng)前矩陣崎场,前乘或者后乘就是相對于這個當(dāng)前矩陣N而言


7.3 補充 2016.09.30 09:09 <p>

根據(jù)總結(jié),看下下面的兩個小練習(xí):

//方式1
matrix = new Matrix();
matrix.preRotate(30);
matrix.postTranslate(100f, 100f);

//方式2
matrix = new Matrix();
matrix.postTranslate(100f, 100f);
matrix.preRotate(30);

//方式3
matrix = new Matrix();
matrix.postRotate(30);
matrix.preTranslate(100f, 100f);
  1. 方式1和方式2結(jié)果是否相同遂蛀? 相同
  2. 方式1和方式3結(jié)果是否相同谭跨? 不同

有圖,有真相

3種方式的差別

在看問題前李滴,先了解這樣一個矩陣的知識點螃宙,有助于理解問題:

有3個矩陣AB所坯,C谆扎,相乘,N = A * B * C
從左向右順序計算芹助,第1步堂湖,X = A * B,然后N = X * C
從右向左倒序計算状土,第1步无蜂,X = B * C,然后N = A * X

這兩種計算方式是一樣的蒙谓。

網(wǎng)上有人說斥季,圖形處理時,矩陣的運算是從右向左計算的累驮,這也就是為啥有pre可以理解為先進行計算的一說酣倾,但個人感覺渊迁,從左開始和從右開始計算是一樣的。但從右開始計算更容易理解吧

之所以說矩陣不滿足乘法的交換規(guī)律灶挟,是說A * BB * A

N = A * B * C琉朽,從左開始計算和從右開始計算結(jié)果一樣的前提就是,要按照矩陣排列時的順序來進行計算

N = A * B稚铣,但N * CC * N

下面看問題


問題1:

  • 方式1的矩陣的形式:
方式1矩陣
  • 方式2的矩陣的形式:
方式2

new Matrix()或者mMatrix.reset()得到的就是一個原始矩陣

兩個矩陣最終形式其實就是一個矩陣箱叁,差別可以通過括號的位置理解,括號內(nèi)的就是先計算的惕医。方式1是可以看作從右面開始計算耕漱,方式2可以看做從左面開始計算。但前面說了抬伺,計算的順序并會不影響最終的結(jié)果


問題2:

方式3的矩陣形式與方式1螟够,2不一樣:

方式3矩陣

方式3中與方式1,2的差別就是旋轉(zhuǎn)和平移兩個矩陣交換了位置峡钓,而矩陣不滿足乘法的交換律妓笙,所以方式1和方式3,最終結(jié)果就不同


8.其他的方法

Matrix中能岩,方法主要4大類寞宫,占據(jù)了絕大部分,set拉鹃,pre辈赋,postmap開頭的方法

8.1 setPolyToPoly()

這個方法非常強大膏燕,通過改變參數(shù)钥屈,除了可以實現(xiàn)平移,旋轉(zhuǎn)坝辫,縮放篷就,錯切,還可以實現(xiàn)透視

這個方法主要是利用確定矩形4個頂點阀溶,根據(jù)4個頂點坐標的變化來對bitmap進行變換

setPolyToPoly方法

最終的效果主要由srcdst兩個數(shù)組進行控制腻脏,兩個數(shù)組控制4個頂點的坐標,srcIndex,dstIndex分別是srcdst的第一個值的角標银锻,pointCount是4個頂點中要使用的個數(shù)永品,最大為4,0表示不進行操作變換


透視效果,簡單使用:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    float bWidth = bitmap.getWidth();
    float bHeight = bitmap.getHeight();
    float[] src = {0, 0, 0, bHeight, bWidth, bHeight,bWidth, 0};
    float[] dst = {0 + 150,0, 0, bHeight, bWidth, bHeight, bWidth - 150, 0};
    matrix.setPolyToPoly(src, 0, dst, 0, 4);
}
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    canvas.drawBitmap(bitmap,matrix,null);
}
透視效果

float[] srcfloat[] dst中的值一定是成對的出現(xiàn)击纬,因為一個點的坐標由(x,y)來確定鼎姐,兩兩一對控制對應(yīng)的一個頂點的坐標,最多有4對有效,超過的無效炕桨,因為再方法setPolyToPoly()中饭尝,最后一個參數(shù)不能超過4


數(shù)組值和頂點坐標點的對應(yīng)關(guān)系:

float[] dst = {f0, f1, f3, f3, f4, f5,f6, f7}
坐標和頂點的對應(yīng)關(guān)系

為了方便看,將bitmap放在了畫布比較靠中心的位置

dst可以看做是底板献宫,最終要顯示的效果钥平;
src可以看做是要截取的bitmap的要顯示的有效區(qū)域

控制不同的點的效果:

  • 1個點,平移
  • 2個點姊途,縮放或者旋轉(zhuǎn)
  • 3個點涉瘾,錯切
  • 4個點,透視

8.2 setRectToRect()

setRectToRect(RectF src, RectF dst, ScaleToFit stf)

第一個參數(shù)src捷兰,截取資源Bitmap的顯示區(qū)域
第二個參數(shù)dst立叛,底板,顯示的區(qū)域
第三個參數(shù)stf贡茅,模式

谷歌api 給的Demo:

ScaleToFit

FILL: 可能會變換矩形的長寬比秘蛇,保證變換和目標矩陣長寬一致。
START:保持坐標變換前矩形的長寬比顶考,并最大限度的填充變換后的矩形赁还。至少有一邊和目標矩形重疊。左上對齊村怪。
CENTER: 保持坐標變換前矩形的長寬比秽浇,并最大限度的填充變換后的矩形浮庐。至少有一邊和目標矩形重疊甚负。
END:保持坐標變換前矩形的長寬比,并最大限度的填充變換后的矩形审残。至少有一邊和目標矩形重疊梭域。右下對齊。

圖和文字說明從androidmatrix最全方法詳解與進階(完整篇)摘抄


簡單使用:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    matrix = new Matrix();
    int screenWidth  = getResources().getDisplayMetrics().widthPixels;
    int screenHeight = getResources().getDisplayMetrics().heightPixels;
    float bWidth = bitmap.getWidth();
    float bHeight = bitmap.getHeight();
    RectF src = new RectF(0,0,bWidth/2,bHeight/2 );
    RectF dst = new RectF(0,0,screenWidth,screenHeight);
    matrix.setRectToRect(src,dst, Matrix.ScaleToFit.END);
}
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    canvas.drawBitmap(bitmap,matrix,null);
}
Matrix.ScaleToFit.END

結(jié)合上面的圖搅轿,也比較容易理解


8.3 其他的一些方法

方法名 作用
reset() 將矩陣恢復(fù)為初始化矩陣
boolean invert(Matrix inverse) 反轉(zhuǎn)當(dāng)前矩陣
boolean isIdentity() 是否為初始化矩陣
boolean isAffine() 是否為仿射矩陣
boolean rectStaysRect() 判斷該矩陣是否可以將一個矩形依然變換為一個矩形病涨。當(dāng)矩陣是單位矩陣,或者只進行平移璧坟,縮放既穆,以及旋轉(zhuǎn)90度的倍數(shù)的時候,返回true

仿射變換其實就是二維坐標到二維坐標的線性變換雀鹃,保持二維圖形的“平直性”(即變換后直線還是直線不會打彎幻工,圓弧還是圓弧)和“平行性”(指保持二維圖形間的相對位置關(guān)系不變黎茎,平行線還是平行線囊颅,而直線上點的位置順序不變),可以通過一系列的原子變換的復(fù)合來實現(xiàn),原子變換就包括:平移踢代、縮放盲憎、翻轉(zhuǎn)、旋轉(zhuǎn)和錯切胳挎。這里除了透視可以改變z軸以外饼疙,其他的變換基本都是上述的原子變換,所以慕爬,只要最后一行是0,0,1則是仿射矩陣宏多。

其他的方法以后用到了再學(xué)習(xí)補充


9. 最后

本篇主要就學(xué)習(xí)4種基本變換操作的方法

本人很菜,有錯誤請指出

感謝學(xué)習(xí)資料中的大神前輩們

共勉 : )

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末澡罚,一起剝皮案震驚了整個濱河市伸但,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌留搔,老刑警劉巖更胖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異隔显,居然都是意外死亡却妨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門括眠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彪标,“玉大人,你說我怎么就攤上這事掷豺±萄蹋” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵当船,是天一觀的道長题画。 經(jīng)常有香客問我,道長德频,這世上最難降的妖魔是什么苍息? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮壹置,結(jié)果婚禮上竞思,老公的妹妹穿的比我還像新娘。我一直安慰自己钞护,他們只是感情好盖喷,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著患亿,像睡著了一般传蹈。 火紅的嫁衣襯著肌膚如雪押逼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天惦界,我揣著相機與錄音挑格,去河邊找鬼。 笑死沾歪,一個胖子當(dāng)著我的面吹牛漂彤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播灾搏,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼挫望,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了狂窑?” 一聲冷哼從身側(cè)響起媳板,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泉哈,沒想到半個月后蛉幸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡丛晦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年奕纫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烫沙。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡匹层,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锌蓄,到底是詐尸還是另有隱情升筏,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布煤率,位于F島的核電站仰冠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蝶糯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一辆沦、第九天 我趴在偏房一處隱蔽的房頂上張望昼捍。 院中可真熱鬧,春花似錦肢扯、人聲如沸妒茬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乍钻。三九已至肛循,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間银择,已是汗流浹背多糠。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浩考,地道東北人夹孔。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像析孽,于是被迫代替她去往敵國和親搭伤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容