mirror of
				https://codeberg.org/tom79/Fedilab.git
				synced 2025-10-20 11:20:16 +03:00 
			
		
		
		
	Fix issue #219 - Use focus point for image preview
This commit is contained in:
		
							parent
							
								
									383702bf6a
								
							
						
					
					
						commit
						0f513f00c4
					
				
					 3 changed files with 257 additions and 0 deletions
				
			
		|  | @ -43,7 +43,36 @@ public class Attachment implements Serializable { | |||
|     public long size; | ||||
|     @SerializedName("local_path") | ||||
|     public String local_path; | ||||
|     @SerializedName("meta") | ||||
|     public Meta meta; | ||||
| 
 | ||||
|     public String peertubeHost = null; | ||||
|     public String peertubeId = null; | ||||
| 
 | ||||
|     public static class Meta implements Serializable { | ||||
|         @SerializedName("focus") | ||||
|         public Focus focus; | ||||
|         @SerializedName("original") | ||||
|         public MediaData original; | ||||
|         @SerializedName("small") | ||||
|         public MediaData small; | ||||
|     } | ||||
| 
 | ||||
|     public static class Focus implements Serializable { | ||||
|         @SerializedName("x") | ||||
|         public float x; | ||||
|         @SerializedName("y") | ||||
|         public float y; | ||||
|     } | ||||
| 
 | ||||
|     public static class MediaData implements Serializable { | ||||
|         @SerializedName("width") | ||||
|         public int width; | ||||
|         @SerializedName("height") | ||||
|         public int height; | ||||
|         @SerializedName("size") | ||||
|         public String size; | ||||
|         @SerializedName("aspect") | ||||
|         public float aspect; | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										209
									
								
								app/src/main/java/app/fedilab/android/helper/GlideFocus.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								app/src/main/java/app/fedilab/android/helper/GlideFocus.java
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,209 @@ | |||
| package app.fedilab.android.helper; | ||||
| /* Copyright 2021 Thomas Schneider | ||||
|  * | ||||
|  * This file is a part of Fedilab | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it under the terms of the | ||||
|  * GNU General Public License as published by the Free Software Foundation; either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even | ||||
|  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | ||||
|  * Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License along with Fedilab; if not, | ||||
|  * see <http://www.gnu.org/licenses>. */ | ||||
| 
 | ||||
| import static com.bumptech.glide.load.resource.bitmap.TransformationUtils.PAINT_FLAGS; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Matrix; | ||||
| import android.graphics.Paint; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; | ||||
| import com.bumptech.glide.load.resource.bitmap.TransformationUtils; | ||||
| import com.bumptech.glide.util.Synthetic; | ||||
| 
 | ||||
| import java.security.MessageDigest; | ||||
| import java.util.Arrays; | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.locks.Condition; | ||||
| import java.util.concurrent.locks.Lock; | ||||
| import java.util.concurrent.locks.ReentrantLock; | ||||
| 
 | ||||
| import jp.wasabeef.glide.transformations.BitmapTransformation; | ||||
| 
 | ||||
| public class GlideFocus extends BitmapTransformation { | ||||
| 
 | ||||
| 
 | ||||
|     private static final int VERSION = 1; | ||||
|     private static final String ID = "app.fedilab.android.GlideFocus." + VERSION; | ||||
|     private static final Set<String> MODELS_REQUIRING_BITMAP_LOCK = | ||||
|             new HashSet<>( | ||||
|                     Arrays.asList( | ||||
|                             // Moto X gen 2 | ||||
|                             "XT1085", | ||||
|                             "XT1092", | ||||
|                             "XT1093", | ||||
|                             "XT1094", | ||||
|                             "XT1095", | ||||
|                             "XT1096", | ||||
|                             "XT1097", | ||||
|                             "XT1098", | ||||
|                             // Moto G gen 1 | ||||
|                             "XT1031", | ||||
|                             "XT1028", | ||||
|                             "XT937C", | ||||
|                             "XT1032", | ||||
|                             "XT1008", | ||||
|                             "XT1033", | ||||
|                             "XT1035", | ||||
|                             "XT1034", | ||||
|                             "XT939G", | ||||
|                             "XT1039", | ||||
|                             "XT1040", | ||||
|                             "XT1042", | ||||
|                             "XT1045", | ||||
|                             // Moto G gen 2 | ||||
|                             "XT1063", | ||||
|                             "XT1064", | ||||
|                             "XT1068", | ||||
|                             "XT1069", | ||||
|                             "XT1072", | ||||
|                             "XT1077", | ||||
|                             "XT1078", | ||||
|                             "XT1079")); | ||||
|     private static final Lock BITMAP_DRAWABLE_LOCK = | ||||
|             MODELS_REQUIRING_BITMAP_LOCK.contains(Build.MODEL) ? new ReentrantLock() : new NoLock(); | ||||
|     private static final Paint DEFAULT_PAINT = new Paint(PAINT_FLAGS); | ||||
|     private final float focalX; | ||||
|     private final float focalY; | ||||
| 
 | ||||
|     public GlideFocus(float focalX, float focalY) { | ||||
|         this.focalX = focalX; | ||||
|         this.focalY = focalY; | ||||
|     } | ||||
| 
 | ||||
|     private static void applyMatrix( | ||||
|             @NonNull Bitmap inBitmap, @NonNull Bitmap targetBitmap, Matrix matrix) { | ||||
|         BITMAP_DRAWABLE_LOCK.lock(); | ||||
|         try { | ||||
|             Canvas canvas = new Canvas(targetBitmap); | ||||
|             canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT); | ||||
|             clear(canvas); | ||||
|         } finally { | ||||
|             BITMAP_DRAWABLE_LOCK.unlock(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private static Bitmap.Config getNonNullConfig(@NonNull Bitmap bitmap) { | ||||
|         return bitmap.getConfig() != null ? bitmap.getConfig() : Bitmap.Config.ARGB_8888; | ||||
|     } | ||||
| 
 | ||||
|     // Avoids warnings in M+. | ||||
|     private static void clear(Canvas canvas) { | ||||
|         canvas.setBitmap(null); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Bitmap transform(@NonNull Context context, @NonNull BitmapPool pool, | ||||
|                                @NonNull Bitmap inBitmap, int width, int height) { | ||||
| 
 | ||||
|         if (inBitmap.getWidth() == width && inBitmap.getHeight() == height) { | ||||
|             return inBitmap; | ||||
|         } | ||||
|         // From ImageView/Bitmap.createScaledBitmap. | ||||
|         final float scale; | ||||
|         final float dx; | ||||
|         final float dy; | ||||
|         Matrix m = new Matrix(); | ||||
|         if (inBitmap.getWidth() * height > width * inBitmap.getHeight()) { | ||||
|             scale = (float) height / (float) inBitmap.getHeight(); | ||||
|             dx = (width - inBitmap.getWidth() * scale) * 0.5f * (1 + focalX); | ||||
|             dy = 0; | ||||
|         } else { | ||||
|             scale = (float) width / (float) inBitmap.getWidth(); | ||||
|             dx = 0; | ||||
|             dy = (height - inBitmap.getHeight() * scale) * 0.5f * (1 + focalY); | ||||
|         } | ||||
| 
 | ||||
|         m.setScale(scale, scale); | ||||
|         m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); | ||||
| 
 | ||||
|         Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap)); | ||||
|         // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. | ||||
|         TransformationUtils.setAlpha(inBitmap, result); | ||||
| 
 | ||||
|         applyMatrix(inBitmap, result, m); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { | ||||
|         messageDigest.update((ID + focalX + focalY).getBytes(CHARSET)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean equals(Object o) { | ||||
|         return o instanceof GlideFocus && | ||||
|                 ((GlideFocus) o).focalX == focalX && | ||||
|                 ((GlideFocus) o).focalY == focalY; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         return (int) (ID.hashCode() + focalX * 100000 + focalY * 1000); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "CropTransformation(width=" + focalX + ", height=" + focalY + ")"; | ||||
|     } | ||||
| 
 | ||||
|     private static final class NoLock implements Lock { | ||||
| 
 | ||||
|         @Synthetic | ||||
|         NoLock() { | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void lock() { | ||||
|             // do nothing | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void lockInterruptibly() throws InterruptedException { | ||||
|             // do nothing | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean tryLock() { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean tryLock(long time, @NonNull TimeUnit unit) throws InterruptedException { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void unlock() { | ||||
|             // do nothing | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public Condition newCondition() { | ||||
|             throw new UnsupportedOperationException("Should not be called"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -117,6 +117,7 @@ import app.fedilab.android.databinding.LayoutMediaBinding; | |||
| import app.fedilab.android.databinding.LayoutPollItemBinding; | ||||
| import app.fedilab.android.exception.DBException; | ||||
| import app.fedilab.android.helper.CrossActionHelper; | ||||
| import app.fedilab.android.helper.GlideFocus; | ||||
| import app.fedilab.android.helper.Helper; | ||||
| import app.fedilab.android.helper.LongClickLinkMovementMethod; | ||||
| import app.fedilab.android.helper.MastodonHelper; | ||||
|  | @ -984,10 +985,18 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | |||
|                 } else { | ||||
|                     layoutMediaBinding.playMusic.setVisibility(View.GONE); | ||||
|                 } | ||||
|                 float focusX = 0.f; | ||||
|                 float focusY = 0.f; | ||||
|                 if (statusToDeal.media_attachments.get(0).meta != null && statusToDeal.media_attachments.get(0).meta.focus != null) { | ||||
|                     focusX = statusToDeal.media_attachments.get(0).meta.focus.x; | ||||
|                     focusY = statusToDeal.media_attachments.get(0).meta.focus.y; | ||||
|                 } | ||||
| 
 | ||||
|                 if (!mediaObfuscated(statusToDeal) || expand_media) { | ||||
|                     layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_24); | ||||
|                     Glide.with(layoutMediaBinding.media.getContext()) | ||||
|                             .load(statusToDeal.media_attachments.get(0).preview_url) | ||||
|                             .apply(new RequestOptions().transform(new GlideFocus(focusX, focusY))) | ||||
|                             .into(layoutMediaBinding.media); | ||||
|                 } else { | ||||
|                     layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24); | ||||
|  | @ -995,6 +1004,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | |||
|                             .load(statusToDeal.media_attachments.get(0).preview_url) | ||||
|                             .apply(new RequestOptions().transform(new BlurTransformation(50, 3))) | ||||
|                             // .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) | ||||
|                             .apply(new RequestOptions().transform(new GlideFocus(focusX, focusY))) | ||||
|                             .into(layoutMediaBinding.media); | ||||
|                 } | ||||
|                 layoutMediaBinding.viewHide.setOnClickListener(v -> { | ||||
|  | @ -1009,6 +1019,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | |||
|                 for (Attachment attachment : statusToDeal.media_attachments) { | ||||
|                     LayoutMediaBinding layoutMediaBinding = LayoutMediaBinding.inflate(LayoutInflater.from(context), holder.binding.attachmentsList, false); | ||||
|                     RelativeLayout.LayoutParams lp; | ||||
|                     float focusX = 0.f; | ||||
|                     float focusY = 0.f; | ||||
|                     if (statusToDeal.media_attachments.get(0).meta != null && statusToDeal.media_attachments.get(0).meta.focus != null) { | ||||
|                         focusX = statusToDeal.media_attachments.get(0).meta.focus.x; | ||||
|                         focusY = statusToDeal.media_attachments.get(0).meta.focus.y; | ||||
|                     } | ||||
| 
 | ||||
|                     if (fullAttachement) { | ||||
|                         lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); | ||||
|                         layoutMediaBinding.media.setScaleType(ImageView.ScaleType.FIT_CENTER); | ||||
|  | @ -1032,12 +1049,14 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | |||
|                         Glide.with(layoutMediaBinding.media.getContext()) | ||||
|                                 .load(attachment.preview_url) | ||||
|                                 .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) | ||||
|                                 .apply(new RequestOptions().transform(new GlideFocus(focusX, focusY))) | ||||
|                                 .into(layoutMediaBinding.media); | ||||
|                     } else { | ||||
|                         layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24); | ||||
|                         Glide.with(layoutMediaBinding.media.getContext()) | ||||
|                                 .load(attachment.preview_url) | ||||
|                                 .apply(new RequestOptions().transform(new BlurTransformation(50, 3))) | ||||
|                                 .apply(new RequestOptions().transform(new GlideFocus(focusX, focusY))) | ||||
|                                 //    .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) | ||||
|                                 .into(layoutMediaBinding.media); | ||||
|                     } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue