Android建筑图像过滤器,如Instagram(1)

Android建筑图像过滤器,如Instagram

现在图像过滤器在很多Android应用程序中很常见。Instagram以其流行的过滤器功能而闻名,可能是第一款将图像过滤器引入Android世界的应用程序。还有很多其他图像编辑应用程序提供图像过滤器和图像编辑功能。

在本文中,我们将学习如何构建像Instagram这样的图像过滤器应用程序。我们不会介绍精确的过滤器开发,但我们使用现有的图像过滤器库。

下载代码下载.APK GITHUB

1.如何构建图像滤镜

通常,图像处理操作将以本机C / C ++语言完成。在android中,您可以使用C或C ++编写库,并使用JNI(Java Native Interface)通过Java代码访问函数。您还可以考虑使用像openCV这样的流行图像处理库来创建自己的过滤器库。

在编写本机模块时,配置JNI是一个单独的主题,我想在另一篇文章中介绍它。现在我们将考虑使用本文中的现有图像过滤器库。

2.使用图书馆(Zomato,Androidhive)

在这篇文章中,我认为使用图像滤波器库AndroidPhotoFilters通过开发Zomato。该库提供基本的图像操作,如控制亮度饱和度对比度和少量图像滤镜。将所有这些功能组合在一起,您可以创建您赢得的过滤器。我对库进行了一些即兴创作并将其托管在公共maven存储库中,以便您可以非常轻松地集成到您的项目中。

还记得库是非常基本的,你不能用Instagram实现像Instagram这样的伟大过滤器。要像Instagram一样构建过滤器,许多基础工作必须在本机级别完成。但我们会尝试实现类似于Instagram的过滤器。

要使用该库,请在项目的依赖项中添加info.androidhive:imagefilters:1.0.7

dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // ...


    implementation 'info.androidhive:imagefilters:1.0.7'

}

有关该库的更详细的文档可以在Github页面上找到。

3.图像过滤包

我写了几个图像过滤器并将它们包含在库中。您可以使用下面的代码段获取Filter Pack。您可以循环它们并渲染每个过滤器的缩略图版本。您也可以直接访问单个过滤器。

// get the filter pack

List filters = FilterPack.getFilterPack(getActivity());


for(Filter filter : filters) {

        ThumbnailItem item = newThumbnailItem();

        item.image = thumbImage;

        item.filter = filter;

        item.filterName = filter.getName();

        ThumbnailsManager.addThumb(tI);

}


// Accessing single filter...

Bitmap bitmap = your_bitmap_;

Filter clarendon = FilterPack.getClarendon();

// apply filter

imagePreview.setImageBitmap(filter.processFilter(bitmap));

下面是每个过滤器的预览及其名称。

过滤包

施特鲁克克拉伦登奥德曼

火星上升四月

亚马逊星光耳语

青柠阿里汉Bluemess

阿黛尔克鲁兹都会

奥黛丽  

4.构建Instagram界面

这里的想法是像Instagram一样构建界面,底部有两个标签。一个选项卡用于应用不同的滤镜,另一个用于控制图像调整,如亮度,对比度和饱和度。

要实现此布局,我们需要将ViewPagerTabLayout结合使用。要在可滚动列表中呈现缩略图图像,需要使用RecyclerView。我们还需要两个Fragment类,一个用于渲染水平缩略图以预览滤镜效果。另一个片段是显示图像控件。

如果您观察下面的图像,MainActivity.java用于实现整个布局。ImageFiltersFragment.java用于渲染水平缩略图图像。EditImageFrament.java用于渲染图像调整控件。每当应用过滤器或更改图像控件时,这两个片段都将提供回调方法。在主要活动中,将在回调时采取适当的行动。

现在让我们从Android Studio中的一个新项目开始吧。

1。从File⇒NewProject在Android Studio中创建一个新项目,然后从模板中选择Basic Activity

2。开放的build.gradle位于下的应用程序文件夹,并添加androidhive的ImageFilters依赖。我还添加了其他必要的依赖,如ButterKnifeDexterRecyclerView

的build.gradle

dependencies {

    // ...


    // image filters

    implementation 'info.androidhive:imagefilters:1.0.7'


    // butter knife

    compile 'com.jakewharton:butterknife:8.8.1'

    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'


    // dexter M permissions

    compile 'com.karumi:dexter:4.1.0'


    compile 'com.android.support:recyclerview-v7:26.1.0'

}

3。将以下资源添加到相应的strings.xmlcolors.xmldimens.xmlstyles.xml文件中。

strings.xml中

    Image Filters

    Filters

    Settings

    FILTERS

    EDIT


    BRIGHTNESS

    CONTRAST

    SATURATION

    FILTERS

    EDIT



    sans-serif-medium

    Normal

    SAVE

    OPEN

colors.xml


    #3F51B5

    #303F9F

    #009688

    #FF3990

    #8A8889

    #221F20

dimens.xml

    16dp

    80dp

    100dp

    8dp

    10dp

    10dp

    16dp

    100dp

styles.xml





        @color/colorPrimary

        @color/colorPrimaryDark

        @color/colorAccent




        false

        true




        false

        true

        true

        @color/color_option_menu







4。打开AndroidManifest.xml并添加READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限。将AppTheme.NoActionBar.Fullscreen主题添加到主活动以使活动全屏显示。

AndroidManifest.xml中


http://schemas.android.com/apk/res/android"

    package="info.androidhive.imagefilters">






        android:allowBackup="true"

        android:icon="@mipmap/ic_launcher"

        android:label="@string/app_name"

        android:roundIcon="@mipmap/ic_launcher_round"

        android:supportsRtl="true"

        android:theme="@style/AppTheme">


            android:name=".MainActivity"

            android:label="@string/app_name"

            android:theme="@style/AppTheme.NoActionBar.Fullscreen">









5。打开位于res⇒ 菜单文件夹下的menu_main.xml,然后修改菜单项,如下所示。此菜单提供工具栏中的OPENSAVE选项以打开和保存图像。

menu_main.xml

http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    tools:context="info.androidhive.imagefilters.MainActivity">


        android:id="@+id/action_open"

        android:orderInCategory="100"

        android:title="@string/action_open"

        app:showAsAction="always"/>



        android:id="@+id/action_save"

        android:orderInCategory="101"

        android:title="@string/action_save"

        app:showAsAction="always"/>

6。创建一个名为utils的新包。在这里,我们将保留此应用程序所需的辅助类。

7。在utils包下,创建一个名为BitmapUtils.java的类。该类将具有从库加载图像,压缩图像,将图像保存到图库的方法。

BitmapUtils.java

packageinfo.androidhive.imagefilters.utils;


importandroid.content.ContentResolver;

importandroid.content.ContentUris;

importandroid.content.ContentValues;

importandroid.content.Context;

importandroid.content.res.AssetManager;

importandroid.content.res.Resources;

importandroid.database.Cursor;

importandroid.graphics.Bitmap;

importandroid.graphics.BitmapFactory;

importandroid.graphics.Matrix;

importandroid.net.Uri;

importandroid.provider.MediaStore;

importandroid.util.Log;


importjava.io.FileNotFoundException;

importjava.io.IOException;

importjava.io.InputStream;

importjava.io.OutputStream;


/**

 * Created by ravi on 06/11/17.

 */


publicclassBitmapUtils {


    privatestaticfinalString TAG = BitmapUtils.class.getSimpleName();


    /**

     * Getting bitmap from Assets folder

     *

     * @return

     */

    publicstaticBitmap getBitmapFromAssets(Context context, String fileName, intwidth, intheight) {

        AssetManager assetManager = context.getAssets();


        InputStream istr;

        Bitmap bitmap = null;

        try{

            finalBitmapFactory.Options options = newBitmapFactory.Options();

            options.inJustDecodeBounds = true;


            istr = assetManager.open(fileName);


            // Calculate inSampleSize

            options.inSampleSize = calculateInSampleSize(options, width, height);


            // Decode bitmap with inSampleSize set

            options.inJustDecodeBounds = false;

            returnBitmapFactory.decodeStream(istr, null, options);

        } catch(IOException e) {

            Log.e(TAG, "Exception: "+ e.getMessage());

        }


        returnnull;

    }


    /**

     * Getting bitmap from Gallery

     *

     * @return

     */

    publicstaticBitmap getBitmapFromGallery(Context context, Uri path, intwidth, intheight) {

        String[] filePathColumn = {MediaStore.Images.Media.DATA};

        Cursor cursor = context.getContentResolver().query(path, filePathColumn, null, null, null);

        cursor.moveToFirst();

        intcolumnIndex = cursor.getColumnIndex(filePathColumn[0]);

        String picturePath = cursor.getString(columnIndex);

        cursor.close();


        finalBitmapFactory.Options options = newBitmapFactory.Options();

        options.inJustDecodeBounds = true;

        BitmapFactory.decodeFile(picturePath, options);


        // Calculate inSampleSize

        options.inSampleSize = calculateInSampleSize(options, width, height);


        // Decode bitmap with inSampleSize set

        options.inJustDecodeBounds = false;

        returnBitmapFactory.decodeFile(picturePath, options);

    }


    privatestaticintcalculateInSampleSize(

            BitmapFactory.Options options, intreqWidth, intreqHeight) {

        // Raw height and width of image

        finalintheight = options.outHeight;

        finalintwidth = options.outWidth;

        intinSampleSize = 1;


        if(height > reqHeight || width > reqWidth) {


            finalinthalfHeight = height / 2;

            finalinthalfWidth = width / 2;


            // Calculate the largest inSampleSize value that is a power of 2 and keeps both

            // height and width larger than the requested height and width.

            while((halfHeight / inSampleSize) >= reqHeight

                    && (halfWidth / inSampleSize) >= reqWidth) {

                inSampleSize *= 2;

            }

        }


        returninSampleSize;

    }


    publicstaticBitmap decodeSampledBitmapFromResource(Resources res, intresId,

                                                         intreqWidth, intreqHeight) {


        // First decode with inJustDecodeBounds=true to check dimensions

        finalBitmapFactory.Options options = newBitmapFactory.Options();

        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(res, resId, options);


        // Calculate inSampleSize

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);


        // Decode bitmap with inSampleSize set

        options.inJustDecodeBounds = false;

        returnBitmapFactory.decodeResource(res, resId, options);

    }


    /**

     * Storing image to device gallery

     * @param cr

     * @param source

     * @param title

     * @param description

     * @return

     */

    publicstaticfinalString insertImage(ContentResolver cr,

                                           Bitmap source,

                                           String title,

                                           String description) {


        ContentValues values = newContentValues();

        values.put(MediaStore.Images.Media.TITLE, title);

        values.put(MediaStore.Images.Media.DISPLAY_NAME, title);

        values.put(MediaStore.Images.Media.DESCRIPTION, description);

        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

        // Add the date meta data to ensure the image is added at the front of the gallery

        values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis());

        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());


        Uri url = null;

        String stringUrl = null;    /* value to be returned */


        try{

            url = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);


            if(source != null) {

                OutputStream imageOut = cr.openOutputStream(url);

                try{

                    source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);

                } finally{

                    imageOut.close();

                }


                longid = ContentUris.parseId(url);

                // Wait until MINI_KIND thumbnail is generated.

                Bitmap miniThumb = MediaStore.Images.Thumbnails.getThumbnail(cr, id, MediaStore.Images.Thumbnails.MINI_KIND, null);

                // This is for backward compatibility.

                storeThumbnail(cr, miniThumb, id, 50F, 50F, MediaStore.Images.Thumbnails.MICRO_KIND);

            } else{

                cr.delete(url, null, null);

                url = null;

            }

        } catch(Exception e) {

            if(url != null) {

                cr.delete(url, null, null);

                url = null;

            }

        }


        if(url != null) {

            stringUrl = url.toString();

        }


        returnstringUrl;

    }


    /**

     * A copy of the Android internals StoreThumbnail method, it used with the insertImage to

     * populate the android.provider.MediaStore.Images.Media#insertImage with all the correct

     * meta data. The StoreThumbnail method is private so it must be duplicated here.

     *

     * @see android.provider.MediaStore.Images.Media (StoreThumbnail private method)

     */

    privatestaticfinalBitmap storeThumbnail(

            ContentResolver cr,

            Bitmap source,

            longid,

            floatwidth,

            floatheight,

            intkind) {


        // create the matrix to scale it

        Matrix matrix = newMatrix();


        floatscaleX = width / source.getWidth();

        floatscaleY = height / source.getHeight();


        matrix.setScale(scaleX, scaleY);


        Bitmap thumb = Bitmap.createBitmap(source, 0, 0,

                source.getWidth(),

                source.getHeight(), matrix,

                true

        );


        ContentValues values = newContentValues(4);

        values.put(MediaStore.Images.Thumbnails.KIND, kind);

        values.put(MediaStore.Images.Thumbnails.IMAGE_ID, (int) id);

        values.put(MediaStore.Images.Thumbnails.HEIGHT, thumb.getHeight());

        values.put(MediaStore.Images.Thumbnails.WIDTH, thumb.getWidth());


        Uri url = cr.insert(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, values);


        try{

            OutputStream thumbOut = cr.openOutputStream(url);

            thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);

            thumbOut.close();

            returnthumb;

        } catch(FileNotFoundException ex) {

            returnnull;

        } catch(IOException ex) {

            returnnull;

        }

    }

}

8。在utils包下,创建一个名为NonSwipeableViewPager.java的类。这是自定义ViewPager元素,用于禁用页面之间的滑动功能

NonSwipeableViewPager.java

packageinfo.androidhive.imagefilters.utils;


importandroid.content.Context;

importandroid.support.v4.view.ViewPager;

importandroid.util.AttributeSet;

importandroid.view.MotionEvent;

importandroid.view.animation.DecelerateInterpolator;

importandroid.widget.Scroller;


importjava.lang.reflect.Field;


/**

 * Created by ravi on 24/10/17.

 * Custom viewpager disabling the swipe

 *https://stackoverflow.com/questions/9650265/how-do-disable-paging-by-swiping-with-finger-in-viewpager-but-still-be-able-to-s

 */


publicclassNonSwipeableViewPager extendsViewPager {


    publicNonSwipeableViewPager(Context context) {

        super(context);

        setMyScroller();

    }


    publicNonSwipeableViewPager(Context context, AttributeSet attrs) {

        super(context, attrs);

        setMyScroller();

    }


    @Override

    publicbooleanonInterceptTouchEvent(MotionEvent event) {

        // Never allow swiping to switch between pages

        returnfalse;

    }


    @Override

    publicbooleanonTouchEvent(MotionEvent event) {

        // Never allow swiping to switch between pages

        returnfalse;

    }


    //down one is added for smooth scrolling


    privatevoidsetMyScroller() {

        try{

            Class viewpager = ViewPager.class;

            Field scroller = viewpager.getDeclaredField("mScroller");

            scroller.setAccessible(true);

            scroller.set(this, newMyScroller(getContext()));

        } catch(Exception e) {

            e.printStackTrace();

        }

    }


    publicclassMyScroller extendsScroller {

        publicMyScroller(Context context) {

            super(context, newDecelerateInterpolator());

        }


        @Override

        publicvoidstartScroll(intstartX, intstartY, intdx, intdy, intduration) {

            super.startScroll(startX, startY, dx, dy, 350/*1 secs*/);

        }

    }

}

9。在utils下创建另一个名为SpacesItemDecoration.java的类。这个类是在RecyclerView缩略图图像周围添加填充。padding-right将添加到所有缩略图图像中,但不会添加到列表中的最后一项。

SpacesItemDecoration.java

packageinfo.androidhive.imagefilters.utils;


importandroid.graphics.Rect;

importandroid.support.v7.widget.RecyclerView;

importandroid.view.View;


/**

 * Created by ravi on 23/10/17.

 */


publicclassSpacesItemDecoration extendsRecyclerView.ItemDecoration {

    privateintspace;


    publicSpacesItemDecoration(intspace) {

        this.space = space;

    }


    @Override

    publicvoidgetItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

        if(parent.getChildAdapterPosition(view) == state.getItemCount() - 1) {

            outRect.left = space;

            outRect.right = 0;

        }else{

            outRect.right = space;

            outRect.left = 0;

        }

    }

}

5.水平缩略图RecyclerView适配器

现在我们准备好了所有必需的课程。在跳转到实际UI之前,让我们创建RecyclerView适配器类。

10。在res⇒布局下创建一个名为thumbnail_list_item.xml的新布局文件。此布局包含TextViewImageView以显示过滤器名称和缩略图图像。

thumbnail_list_item.xml


http://schemas.android.com/apk/res/android"

    android:layout_width="wrap_content"

    android:layout_height="match_parent"

    android:orientation="vertical">



        android:id="@+id/filter_name"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="center_horizontal"

        android:layout_marginBottom="5dp"

        android:layout_marginTop="5dp"

        android:fontFamily="@string/roboto_medium"/>



        android:id="@+id/thumbnail"

        android:layout_width="@dimen/thumbnail_size"

        android:layout_height="@dimen/thumbnail_size"

        android:src="@mipmap/ic_launcher"/>


11。创建一个名为ThumbnailsAdapter.java的类此类充当RecyclerView缩略图适配器,以在水平列表中显示已过滤的图像。

ThumbnailsAdapter.java

packageinfo.androidhive.imagefilters;


importandroid.content.Context;

importandroid.support.v4.content.ContextCompat;

importandroid.support.v7.widget.RecyclerView;

importandroid.view.LayoutInflater;

importandroid.view.View;

importandroid.view.ViewGroup;

importandroid.widget.ImageView;

importandroid.widget.TextView;


importcom.zomato.photofilters.imageprocessors.Filter;

importcom.zomato.photofilters.utils.ThumbnailItem;


importjava.util.List;


importbutterknife.BindView;

importbutterknife.ButterKnife;


/**

 * Created by ravi on 23/10/17.

 */


publicclassThumbnailsAdapter extendsRecyclerView.Adapter {


    privateList thumbnailItemList;

    privateThumbnailsAdapterListener listener;

    privateContext mContext;

    privateintselectedIndex = 0;


    publicclassMyViewHolder extendsRecyclerView.ViewHolder {

        @BindView(R.id.thumbnail)

        ImageView thumbnail;


        @BindView(R.id.filter_name)

        TextView filterName;


        publicMyViewHolder(View view) {

            super(view);


            ButterKnife.bind(this, view);

        }

    }



    publicThumbnailsAdapter(Context context, List thumbnailItemList, ThumbnailsAdapterListener listener) {

        mContext = context;

        this.thumbnailItemList = thumbnailItemList;

        this.listener = listener;

    }


    @Override

    publicMyViewHolder onCreateViewHolder(ViewGroup parent, intviewType) {

        View itemView = LayoutInflater.from(parent.getContext())

                .inflate(R.layout.thumbnail_list_item, parent, false);


        returnnewMyViewHolder(itemView);

    }


    @Override

    publicvoidonBindViewHolder(MyViewHolder holder, finalintposition) {

        finalThumbnailItem thumbnailItem = thumbnailItemList.get(position);


        holder.thumbnail.setImageBitmap(thumbnailItem.image);


        holder.thumbnail.setOnClickListener(newView.OnClickListener() {

            @Override

            publicvoidonClick(View view) {

                listener.onFilterSelected(thumbnailItem.filter);

                selectedIndex = position;

                notifyDataSetChanged();

            }

        });


        holder.filterName.setText(thumbnailItem.filterName);


        if(selectedIndex == position) {

            holder.filterName.setTextColor(ContextCompat.getColor(mContext, R.color.filter_label_selected));

        } else{

            holder.filterName.setTextColor(ContextCompat.getColor(mContext, R.color.filter_label_normal));

        }

    }


    @Override

    publicintgetItemCount() {

        returnthumbnailItemList.size();

    }


    publicinterfaceThumbnailsAdapterListener {

        voidonFilterSelected(Filter filter);

    }

}

6.添加图像过滤器列表片段

现在我们将创建一个Fragment类,以在水平列表中呈现过滤后的图像缩略图。为此,我们需要一个RecyclerView并提供缩略图列表到适配器类。

12。通过转到文件⇒新⇒片段⇒片段(空白)创建一个新片段,并将其命名为FiltersListFragment.java。这将创建一个包含必要布局文件的空白片段。

13。打开片段的布局文件,即fragment_filters_list.xml,并添加RecyclerView元素。

fragment_filters_list.xml

http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context="info.androidhive.imagefilters.FiltersListFragment">



        android:id="@+id/recycler_view"

        android:layout_gravity="center_vertical"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:clipChildren="false"

        android:padding="4dp"

        android:scrollbars="none"/>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容