├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── luoye │ │ │ └── fpic │ │ │ └── ApplicationTest.java │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── luoye │ │ │ │ └── fpic │ │ │ │ ├── MainActivity.java │ │ │ │ └── util │ │ │ │ ├── MediaScanner.java │ │ │ │ └── Utils.java │ │ └── res │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── menu │ │ │ └── main_menu.xml │ │ │ ├── mipmap-hdpi │ │ │ └── icon.png │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── test │ │ └── java │ │ └── com │ │ └── luoye │ │ └── fpic │ │ └── ExampleUnitTest.java ├── 怪图制作1.1.2.apk └── 怪图制作1.1.3.apk ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot └── main.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | 字在图 -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 22 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | 52 | 53 | 1.8 54 | 55 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 用文字来绘图 2 | 3 | 4 | 5 | 核心代码在Utils.java,用文字代替像素块形成图片的算法原理如下: 6 | * 1.根据原图片的大小和字体的大小创建一张空白图片 7 | * 2.把原图片按字体的大小分成若干块,取每一块的像素的颜色的平均值 8 | * 3.将指定文本以得到的平均颜色画在新建的空白图上 9 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.luoye.fpic" 9 | minSdkVersion 15 10 | targetSdkVersion 22 11 | versionCode 5 12 | versionName "1.1.3" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled true 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | 20 | debug { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(include: ['*.jar'], dir: 'libs') 29 | testCompile 'junit:junit:4.12' 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in d:\Users\Administrator\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/luoye/fpic/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.luoye.fpic; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/luoye/fpic/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.luoye.fpic; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.ProgressDialog; 6 | import android.content.Intent; 7 | import android.database.Cursor; 8 | import android.graphics.Bitmap; 9 | import android.graphics.BitmapFactory; 10 | import android.graphics.Color; 11 | import android.net.Uri; 12 | import android.os.Bundle; 13 | import android.os.Environment; 14 | import android.os.Message; 15 | import android.provider.MediaStore; 16 | import android.text.TextUtils; 17 | import android.util.Log; 18 | import android.view.Menu; 19 | import android.view.MenuItem; 20 | import android.view.View; 21 | import android.widget.Button; 22 | import android.widget.EditText; 23 | import android.widget.ImageView; 24 | import android.widget.RadioButton; 25 | import android.widget.RadioGroup; 26 | import android.widget.SeekBar; 27 | import android.widget.TextView; 28 | 29 | import com.luoye.fpic.util.MediaScanner; 30 | import com.luoye.fpic.util.Utils; 31 | 32 | import java.io.ByteArrayOutputStream; 33 | import java.io.File; 34 | import java.io.FileOutputStream; 35 | import android.os.Handler; 36 | import android.widget.Toast; 37 | 38 | import java.io.IOException; 39 | 40 | public class MainActivity extends Activity { 41 | 42 | private static final int IMAGE = 0x100; 43 | private Button selectButton; 44 | private Button convertButton; 45 | private EditText textEdit; 46 | private ImageView imageView; 47 | private SeekBar seekBar; 48 | private TextView seekBarText; 49 | private File imgPath; 50 | private boolean isSelect=false; 51 | private ProgressDialog progressDialog; 52 | private String SDCARD= Environment.getExternalStorageDirectory().getAbsolutePath(); 53 | private ConvertThread convertThread; 54 | private boolean isFinish=false; 55 | private RadioGroup radioGroup; 56 | private RadioButton radioButtonText; 57 | private RadioButton radioButtonBlock; 58 | private EditText backColorEditText; 59 | @Override 60 | protected void onCreate(Bundle savedInstanceState) { 61 | super.onCreate(savedInstanceState); 62 | setContentView(R.layout.activity_main); 63 | selectButton=(Button)findViewById(R.id.select_file_button); 64 | textEdit=(EditText)findViewById(R.id.text_edit) ; 65 | seekBar=(SeekBar)findViewById(R.id.seek_bar); 66 | seekBar.setMax(100); 67 | seekBarText=(TextView)findViewById(R.id.seek_bar_text); 68 | imageView=(ImageView)findViewById(R.id.main_iv); 69 | convertButton=(Button)findViewById(R.id.convert_button); 70 | backColorEditText=(EditText) findViewById(R.id.back_color_edit); 71 | radioGroup=(RadioGroup)findViewById(R.id.main_radio_group); 72 | radioButtonText=(RadioButton)findViewById(R.id.text_radio_button) ; 73 | radioButtonBlock=(RadioButton)findViewById(R.id.block_radio_button) ; 74 | 75 | progressDialog=new ProgressDialog(this); 76 | progressDialog.setMessage("转换中,请稍等..."); 77 | progressDialog.setIndeterminate(false); 78 | progressDialog.setCancelable(false); 79 | 80 | selectButton.setOnClickListener(new MyOnClickEvents()); 81 | convertButton.setOnClickListener(new MyOnClickEvents()); 82 | imageView.setOnClickListener(new MyOnClickEvents()); 83 | 84 | seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 85 | @Override 86 | public void onProgressChanged(SeekBar seekBar, int i, boolean b) { 87 | seekBarText.setText(i+""); 88 | if(i==0) 89 | { 90 | seekBar.setProgress(1); 91 | } 92 | } 93 | 94 | @Override 95 | public void onStartTrackingTouch(SeekBar seekBar) { 96 | 97 | } 98 | 99 | @Override 100 | public void onStopTrackingTouch(SeekBar seekBar) { 101 | 102 | } 103 | }); 104 | radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 105 | @Override 106 | public void onCheckedChanged(RadioGroup radioGroup, int checkId) { 107 | if(checkId==R.id.text_radio_button) 108 | { 109 | textEdit.setEnabled(true); 110 | backColorEditText.setEnabled(true); 111 | }else { 112 | textEdit.setEnabled(false); 113 | backColorEditText.setEnabled(false); 114 | } 115 | } 116 | }); 117 | 118 | } 119 | 120 | private MediaScanner mediaScanner; 121 | private Handler handler=new Handler() { 122 | @Override 123 | public void handleMessage(Message msg) { 124 | if(msg.what==1) 125 | { 126 | byte[] data=(byte[])msg.obj; 127 | if(data==null) 128 | { 129 | showToast("转换失败"); 130 | } 131 | else { 132 | isFinish=true; 133 | imageView.setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)); 134 | if(progressDialog!=null) 135 | progressDialog.dismiss(); 136 | showToast("转换成功,保存路径:"+getOutputFile().getAbsolutePath()); 137 | if (mediaScanner == null) 138 | mediaScanner = new MediaScanner(MainActivity.this); 139 | mediaScanner.scanFile(getOutputFile().getAbsolutePath(), "image/*"); 140 | } 141 | } 142 | } 143 | }; 144 | 145 | 146 | private void shareImage(File imagePath) { 147 | Uri imageUri = Uri.fromFile(imagePath); 148 | 149 | Intent shareIntent = new Intent(); 150 | shareIntent.setAction(Intent.ACTION_SEND); 151 | shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri); 152 | shareIntent.setType("image/*"); 153 | startActivity(Intent.createChooser(shareIntent, "分享到")); 154 | } 155 | 156 | private class MyOnClickEvents implements View.OnClickListener{ 157 | @Override 158 | public void onClick(View view) { 159 | if(view.getId()==R.id.select_file_button) { 160 | Intent intent = new Intent(Intent.ACTION_PICK, 161 | android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 162 | startActivityForResult(intent, IMAGE); 163 | } 164 | else if(view.getId()==R.id.convert_button) 165 | { 166 | if(!isSelect) 167 | { 168 | showToast("请先选择图片"); 169 | return; 170 | } 171 | if(TextUtils.isEmpty(textEdit.getText())&&radioButtonText.isChecked()) 172 | { 173 | showToast("请输入文本"); 174 | return ; 175 | } 176 | if(TextUtils.isEmpty(backColorEditText.getText())&&radioButtonText.isChecked()) 177 | { 178 | showToast("请输入背景颜色值"); 179 | return ; 180 | } 181 | int backColor=0; 182 | try { 183 | backColor= Color.parseColor(backColorEditText.getText().toString());//Integer.parseInt(backColorEditText.getText().toString(),16); 184 | }catch (Exception e) 185 | { 186 | showToast("颜色值输入不正确"); 187 | e.printStackTrace(); 188 | return; 189 | } 190 | File output=getOutputFile(); 191 | if(output==null) 192 | { 193 | showToast("无法写入文件"); 194 | return; 195 | } 196 | 197 | progressDialog.show(); 198 | if(radioButtonBlock.isChecked()) { 199 | convertThread = new ConvertThread(handler, imgPath, output, seekBar.getProgress()); 200 | } 201 | else if(radioButtonText.isChecked()) 202 | { 203 | convertThread = new ConvertThread(handler, imgPath, output,backColor,textEdit.getText().toString(), seekBar.getProgress()); 204 | } 205 | convertThread.start(); 206 | } 207 | else if(view.getId()==R.id.main_iv) 208 | { 209 | //生成了才分享 210 | if(isFinish) 211 | shareImage(getOutputFile()); 212 | } 213 | } 214 | } 215 | private File getOutputFile() { 216 | File dir = new File(SDCARD+File.separator+"Pictures" + File.separator + "LuoyePic"); 217 | if (!dir.exists() ) { 218 | if(!dir.mkdirs()) 219 | { 220 | return null; 221 | } 222 | } 223 | String name = imgPath.getName(); 224 | File file = new File(dir, name); 225 | return file; 226 | } 227 | private class ConvertThread extends Thread 228 | { 229 | private Handler handler; 230 | private File in; 231 | private File out; 232 | private String text; 233 | private int fontSize; 234 | private int style=0; 235 | private int backColor; 236 | public ConvertThread(Handler handler,File in,File out,int backColor,String text,int fontSize) 237 | { 238 | this.handler=handler; 239 | this.in=in; 240 | this.out=out; 241 | this.text=text; 242 | this.fontSize=fontSize; 243 | this.style=0; 244 | this.backColor=backColor; 245 | } 246 | public ConvertThread(Handler handler,File in,File out,int fontSize) 247 | { 248 | this.handler=handler; 249 | this.in=in; 250 | this.out=out; 251 | this.fontSize=fontSize; 252 | this.style=1; 253 | } 254 | @Override 255 | public void run() { 256 | byte[] data=convert(in,out,text,fontSize); 257 | handler.sendMessage(handler.obtainMessage(1,data)); 258 | } 259 | 260 | /** 261 | * 转换 262 | * @param input 263 | * @param output 264 | * @param text 265 | * @param fontSize 266 | */ 267 | private byte[] convert(File input,File output,String text,int fontSize) 268 | { 269 | Bitmap bitmap= BitmapFactory.decodeFile(input.getAbsolutePath()); 270 | Bitmap target=null; 271 | if(style==0) { 272 | target = Utils.getTextBitmap(bitmap,backColor, text, fontSize); 273 | }else 274 | { 275 | target = Utils.getBlockBitmap(bitmap, fontSize); 276 | } 277 | FileOutputStream fileOutputStream=null; 278 | ByteArrayOutputStream byteArrayOutputStream=null; 279 | try { 280 | fileOutputStream=new FileOutputStream(output); 281 | byteArrayOutputStream=new ByteArrayOutputStream(); 282 | 283 | target.compress(Bitmap.CompressFormat.PNG,100,byteArrayOutputStream); 284 | byte[] data=byteArrayOutputStream.toByteArray(); 285 | fileOutputStream.write(data,0,data.length); 286 | fileOutputStream.flush(); 287 | return data; 288 | 289 | } catch (Exception e) { 290 | e.printStackTrace(); 291 | } 292 | finally { 293 | if(fileOutputStream!=null) 294 | { 295 | try { 296 | fileOutputStream.close(); 297 | } catch (IOException e) { 298 | e.printStackTrace(); 299 | } 300 | } 301 | 302 | if(byteArrayOutputStream!=null) 303 | { 304 | try { 305 | byteArrayOutputStream.close(); 306 | } catch (IOException e) { 307 | e.printStackTrace(); 308 | } 309 | } 310 | } 311 | 312 | return null; 313 | } 314 | } 315 | 316 | 317 | 318 | @Override 319 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 320 | if (requestCode == IMAGE && resultCode == Activity.RESULT_OK && data != null) { 321 | Uri selectedImage = data.getData(); 322 | String[] filePathColumns = {MediaStore.Images.Media.DATA}; 323 | Cursor c = getContentResolver().query(selectedImage, filePathColumns, null, null, null); 324 | c.moveToFirst(); 325 | int columnIndex = c.getColumnIndex(filePathColumns[0]); 326 | String imagePath = c.getString(columnIndex); 327 | imgPath=new File(imagePath); 328 | c.close(); 329 | selectButton.setText(imgPath.getName()); 330 | isSelect=true; 331 | } 332 | } 333 | 334 | 335 | @Override 336 | public boolean onOptionsItemSelected(MenuItem item) { 337 | if(item.getItemId()==R.id.help) 338 | { 339 | new AlertDialog.Builder(this).setTitle("帮助") 340 | .setMessage(getResources().getString(R.string.help)) 341 | .setPositiveButton("知道啦",null) 342 | .create().show(); 343 | } 344 | return super.onOptionsItemSelected(item); 345 | } 346 | 347 | @Override 348 | public boolean onCreateOptionsMenu(Menu menu) { 349 | getMenuInflater().inflate(R.menu.main_menu,menu); 350 | return super.onCreateOptionsMenu(menu); 351 | } 352 | 353 | /** 354 | * 显示toast 355 | * @param text 356 | */ 357 | private Toast toast; 358 | private void showToast(CharSequence text) { 359 | if (toast == null) { 360 | toast = Toast.makeText(this, text, Toast.LENGTH_SHORT); 361 | } else { 362 | toast.setText(text); 363 | } 364 | toast.show(); 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /app/src/main/java/com/luoye/fpic/util/MediaScanner.java: -------------------------------------------------------------------------------- 1 | package com.luoye.fpic.util; 2 | 3 | import android.content.Context; 4 | import android.media.MediaScannerConnection; 5 | import android.net.Uri; 6 | 7 | public class MediaScanner implements 8 | MediaScannerConnection.MediaScannerConnectionClient { 9 | 10 | private MediaScannerConnection conn; 11 | private String filePath; 12 | private String mimeType; 13 | 14 | public MediaScanner(Context context) { 15 | conn = new MediaScannerConnection(context, this); 16 | conn.connect(); 17 | } 18 | 19 | public void scanFile(String path, String mime) { 20 | if (conn.isConnected()) 21 | conn.scanFile(path, mime); 22 | else { 23 | filePath = path; 24 | mimeType = mime; 25 | } 26 | } 27 | 28 | public void onMediaScannerConnected() { 29 | if (filePath != null) 30 | conn.scanFile(filePath, mimeType); 31 | 32 | filePath = null; 33 | mimeType = null; 34 | } 35 | 36 | public void onScanCompleted(String path, Uri uri) { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/luoye/fpic/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.luoye.fpic.util; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | 9 | /** 10 | * Created by zyw on 2017/8/17. 11 | */ 12 | public class Utils { 13 | /** 14 | * 核心,文本转成图片 15 | * @param bitmap 原图片 16 | * @param text 文本 17 | * @param fontSize 文字大小 18 | * @return 19 | */ 20 | public static Bitmap getTextBitmap(Bitmap bitmap,int backColor, String text, int fontSize) 21 | { 22 | if(bitmap==null) 23 | throw new IllegalArgumentException("Bitmap cannot be null."); 24 | int picWidth=bitmap.getWidth(); 25 | int picHeight=bitmap.getHeight(); 26 | Bitmap back= Bitmap.createBitmap((bitmap.getWidth()%fontSize==0)?bitmap.getWidth():((bitmap.getWidth()/fontSize)*fontSize) 27 | ,(bitmap.getHeight()%fontSize==0)?bitmap.getHeight():((bitmap.getHeight()/fontSize)*fontSize) 28 | , Bitmap.Config.ARGB_8888); 29 | Canvas canvas=new Canvas(back); 30 | canvas.drawColor(backColor); 31 | int idx=0; 32 | for(int y=0;y>16); 132 | green += ((color&0xff00)>>8); 133 | blue += (color&0x0000ff); 134 | } 135 | float len=colors.length; 136 | //alpha=Math.round(alpha/len); 137 | red=Math.round(red/len); 138 | green=Math.round(green/len); 139 | blue=Math.round(blue/len); 140 | 141 | return Color.argb(0xff,red,green,blue); 142 | } 143 | 144 | private static void log(String log) 145 | { 146 | System.out.println("-------->Utils:"+log); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 |