зеркало из https://github.com/mozilla/gecko-dev.git
Bug 783092: LightweightTheme support in Fennec. [r=mfinkle]
--HG-- extra : rebase_source : 61d66ba4c9a36616156edbbb311670916e2ad23b
This commit is contained in:
Родитель
ef6b2658c3
Коммит
3236c22c49
|
@ -10,6 +10,7 @@ import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.PorterDuffXfermode;
|
import android.graphics.PorterDuffXfermode;
|
||||||
import android.graphics.PorterDuff.Mode;
|
import android.graphics.PorterDuff.Mode;
|
||||||
|
import android.graphics.Shader;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
public class CanvasDelegate {
|
public class CanvasDelegate {
|
||||||
|
@ -68,4 +69,8 @@ public class CanvasDelegate {
|
||||||
// Restore the canvas.
|
// Restore the canvas.
|
||||||
canvas.restoreToCount(count);
|
canvas.restoreToCount(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setShader(Shader shader) {
|
||||||
|
mPaint.setShader(shader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,4 +40,8 @@ public class GeckoActivity extends Activity {
|
||||||
MemoryMonitor.getInstance().onTrimMemory(level);
|
MemoryMonitor.getInstance().onTrimMemory(level);
|
||||||
super.onTrimMemory(level);
|
super.onTrimMemory(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LightweightTheme getLightweightTheme() {
|
||||||
|
return ((GeckoApplication) getApplication()).getLightweightTheme();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ public class GeckoApplication extends Application {
|
||||||
private boolean mInited;
|
private boolean mInited;
|
||||||
private boolean mInBackground;
|
private boolean mInBackground;
|
||||||
|
|
||||||
|
private LightweightTheme mLightweightTheme;
|
||||||
|
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
if (mInited)
|
if (mInited)
|
||||||
return;
|
return;
|
||||||
|
@ -22,6 +24,8 @@ public class GeckoApplication extends Application {
|
||||||
Class.forName("android.os.AsyncTask");
|
Class.forName("android.os.AsyncTask");
|
||||||
} catch (ClassNotFoundException e) {}
|
} catch (ClassNotFoundException e) {}
|
||||||
|
|
||||||
|
mLightweightTheme = new LightweightTheme(this);
|
||||||
|
|
||||||
GeckoConnectivityReceiver.getInstance().init(getApplicationContext());
|
GeckoConnectivityReceiver.getInstance().init(getApplicationContext());
|
||||||
GeckoBatteryManager.getInstance().init(getApplicationContext());
|
GeckoBatteryManager.getInstance().init(getApplicationContext());
|
||||||
GeckoBatteryManager.getInstance().start();
|
GeckoBatteryManager.getInstance().start();
|
||||||
|
@ -50,4 +54,8 @@ public class GeckoApplication extends Application {
|
||||||
public boolean isApplicationInBackground() {
|
public boolean isApplicationInBackground() {
|
||||||
return mInBackground;
|
return mInBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LightweightTheme getLightweightTheme() {
|
||||||
|
return mLightweightTheme;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||||
|
import org.mozilla.gecko.util.GeckoEventListener;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewParent;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class LightweightTheme implements GeckoEventListener {
|
||||||
|
private static final String LOGTAG = "GeckoLightweightTheme";
|
||||||
|
|
||||||
|
private Application mApplication;
|
||||||
|
private Bitmap mBitmap;
|
||||||
|
private int mColor;
|
||||||
|
|
||||||
|
public static interface OnChangeListener {
|
||||||
|
// This is the View's default post.
|
||||||
|
// This is required to post the change/rest on UI thread.
|
||||||
|
public boolean post(Runnable action);
|
||||||
|
|
||||||
|
// The View should change its background/text color.
|
||||||
|
public void onLightweightThemeChanged();
|
||||||
|
|
||||||
|
// The View should reset to its default background/text color.
|
||||||
|
public void onLightweightThemeReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<OnChangeListener> mListeners;
|
||||||
|
|
||||||
|
public LightweightTheme(Application application) {
|
||||||
|
mApplication = application;
|
||||||
|
mListeners = new ArrayList<OnChangeListener>();
|
||||||
|
|
||||||
|
GeckoAppShell.getEventDispatcher().registerEventListener("LightweightTheme:Update", this);
|
||||||
|
GeckoAppShell.getEventDispatcher().registerEventListener("LightweightTheme:Disable", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(final OnChangeListener listener) {
|
||||||
|
// Don't inform the listeners that attached late.
|
||||||
|
// Their onLayout() will take care of them before their onDraw();
|
||||||
|
mListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeListener(OnChangeListener listener) {
|
||||||
|
mListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLightweightTheme(String headerURL) {
|
||||||
|
try {
|
||||||
|
// Wait till gecko downloads and gives us the file, don't download.
|
||||||
|
if (headerURL.indexOf("http") != -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get the image and convert it to a bitmap.
|
||||||
|
URL url = new URL(headerURL);
|
||||||
|
InputStream stream = url.openStream();
|
||||||
|
mBitmap = BitmapFactory.decodeStream(stream);
|
||||||
|
stream.close();
|
||||||
|
|
||||||
|
// To find the dominant color only once, take the bottom 25% of pixels.
|
||||||
|
DisplayMetrics dm = mApplication.getResources().getDisplayMetrics();
|
||||||
|
int maxWidth = Math.max(dm.widthPixels, dm.heightPixels);
|
||||||
|
int height = (int) (mBitmap.getHeight() * 0.25);
|
||||||
|
Bitmap cropped = Bitmap.createBitmap(mBitmap, mBitmap.getWidth() - maxWidth,
|
||||||
|
mBitmap.getHeight() - height,
|
||||||
|
maxWidth, height);
|
||||||
|
mColor = BitmapUtils.getDominantColor(cropped, false);
|
||||||
|
cropped.recycle();
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
} catch(java.net.MalformedURLException e) {
|
||||||
|
mBitmap = null;
|
||||||
|
} catch(java.io.IOException e) {
|
||||||
|
mBitmap = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetLightweightTheme() {
|
||||||
|
// Reset the bitmap.
|
||||||
|
mBitmap = null;
|
||||||
|
|
||||||
|
// Post the reset on the UI thread.
|
||||||
|
for (OnChangeListener listener : mListeners) {
|
||||||
|
final OnChangeListener oneListener = listener;
|
||||||
|
oneListener.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
oneListener.onLightweightThemeReset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyListeners() {
|
||||||
|
if (mBitmap == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Post the change on the UI thread.
|
||||||
|
for (OnChangeListener listener : mListeners) {
|
||||||
|
final OnChangeListener oneListener = listener;
|
||||||
|
oneListener.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
oneListener.onLightweightThemeChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(String event, JSONObject message) {
|
||||||
|
try {
|
||||||
|
if (event.equals("LightweightTheme:Update")) {
|
||||||
|
JSONObject lightweightTheme = message.getJSONObject("data");
|
||||||
|
String headerURL = lightweightTheme.getString("headerURL");
|
||||||
|
int mark = headerURL.indexOf('?');
|
||||||
|
if (mark != -1)
|
||||||
|
headerURL = headerURL.substring(0, mark);
|
||||||
|
setLightweightTheme(headerURL);
|
||||||
|
} else if (event.equals("LightweightTheme:Disable")) {
|
||||||
|
resetLightweightTheme();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crop the image based on the position of the view on the window.
|
||||||
|
* Either the View or one of its ancestors might have scrolled or translated.
|
||||||
|
* This value should be taken into account while mapping the View to the Bitmap.
|
||||||
|
*
|
||||||
|
* @param view The view requesting a cropped bitmap.
|
||||||
|
*/
|
||||||
|
private Bitmap getCroppedBitmap(View view) {
|
||||||
|
if (mBitmap == null || view == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Get the global position of the view on the entire screen.
|
||||||
|
Rect rect = new Rect();
|
||||||
|
view.getGlobalVisibleRect(rect);
|
||||||
|
|
||||||
|
// Get the activity's window position. This does an IPC call, may be expensive.
|
||||||
|
Rect window = new Rect();
|
||||||
|
view.getWindowVisibleDisplayFrame(window);
|
||||||
|
|
||||||
|
// Calculate the coordinates for the cropped bitmap.
|
||||||
|
int screenWidth = view.getContext().getResources().getDisplayMetrics().widthPixels;
|
||||||
|
int left = mBitmap.getWidth() - screenWidth + rect.left;
|
||||||
|
int right = mBitmap.getWidth() - screenWidth + rect.right;
|
||||||
|
int top = rect.top - window.top;
|
||||||
|
int bottom = rect.bottom - window.top;
|
||||||
|
|
||||||
|
int offsetX = 0;
|
||||||
|
int offsetY = 0;
|
||||||
|
|
||||||
|
// Find if this view or any of its ancestors has been translated or scrolled.
|
||||||
|
ViewParent parent;
|
||||||
|
View curView = view;
|
||||||
|
do {
|
||||||
|
if (Build.VERSION.SDK_INT >= 11) {
|
||||||
|
offsetX += (int) curView.getTranslationX() - curView.getScrollX();
|
||||||
|
offsetY += (int) curView.getTranslationY() - curView.getScrollY();
|
||||||
|
} else {
|
||||||
|
offsetX -= curView.getScrollX();
|
||||||
|
offsetY -= curView.getScrollY();
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = curView.getParent();
|
||||||
|
|
||||||
|
if (parent instanceof View)
|
||||||
|
curView = (View) parent;
|
||||||
|
|
||||||
|
} while(parent instanceof View && parent != null);
|
||||||
|
|
||||||
|
// Adjust the coordinates for the offset.
|
||||||
|
left -= offsetX;
|
||||||
|
right -= offsetX;
|
||||||
|
top -= offsetY;
|
||||||
|
bottom -= offsetY;
|
||||||
|
|
||||||
|
// The either the required height may be less than the available image height or more than it.
|
||||||
|
// If the height required is more, crop only the available portion on the image.
|
||||||
|
int width = right - left;
|
||||||
|
int height = (bottom > mBitmap.getHeight() ? mBitmap.getHeight() - top : bottom - top);
|
||||||
|
|
||||||
|
// There is a chance that the view is not visible or doesn't fall within the phone's size.
|
||||||
|
// In this case, 'rect' will have all values as '0'. Hence 'top' and 'bottom' may be negative,
|
||||||
|
// and createBitmap() will fail.
|
||||||
|
// The view will get a background in its next layout pass.
|
||||||
|
try {
|
||||||
|
return Bitmap.createBitmap(mBitmap, left, top, width, height);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the cropped bitmap to a BitmapDrawable and returns the same.
|
||||||
|
*
|
||||||
|
* @param view The view for which a background drawable is required.
|
||||||
|
* @return Either the cropped bitmap as a Drawable or null.
|
||||||
|
*/
|
||||||
|
public Drawable getDrawable(View view) {
|
||||||
|
Bitmap bitmap = getCroppedBitmap(view);
|
||||||
|
if (bitmap == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
BitmapDrawable drawable = new BitmapDrawable(view.getContext().getResources(), bitmap);
|
||||||
|
drawable.setGravity(Gravity.TOP|Gravity.RIGHT);
|
||||||
|
drawable.setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the cropped bitmap to a LightweightThemeDrawable, with the required alpha.
|
||||||
|
* LightweightThemeDrawable is optionally placed over a ColorDrawable (of dominant color),
|
||||||
|
* if the cropped bitmap cannot fill the entire view.
|
||||||
|
*
|
||||||
|
* @param view The view for which a background drawable is required.
|
||||||
|
* @param alpha The alpha (0..255) value to be applied to the Drawable.
|
||||||
|
* @return Either the cropped bitmap as a Drawable or null.
|
||||||
|
*/
|
||||||
|
public Drawable getDrawableWithAlpha(View view, int alpha) {
|
||||||
|
return getDrawableWithAlpha(view, alpha, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the cropped bitmap to a LightweightThemeDrawable, with the required alpha applied as
|
||||||
|
* a LinearGradient. LightweightThemeDrawable is optionally placed over a ColorDrawable
|
||||||
|
* (of dominant color), if the cropped bitmap cannot fill the entire view.
|
||||||
|
*
|
||||||
|
* @param view The view for which a background drawable is required.
|
||||||
|
* @param startAlpha The top alpha (0..255) of the linear gradient to be applied to the Drawable.
|
||||||
|
* @param endAlpha The bottom alpha (0..255) of the linear gradient to be applied to the Drawable.
|
||||||
|
* @return Either the cropped bitmap as a Drawable or null.
|
||||||
|
*/
|
||||||
|
public Drawable getDrawableWithAlpha(View view, int startAlpha, int endAlpha) {
|
||||||
|
Bitmap bitmap = getCroppedBitmap(view);
|
||||||
|
if (bitmap == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
LightweightThemeDrawable drawable = new LightweightThemeDrawable(view.getContext().getResources(), bitmap);
|
||||||
|
drawable.setAlpha(startAlpha, endAlpha);
|
||||||
|
drawable.setGravity(Gravity.TOP|Gravity.RIGHT|Gravity.FILL_HORIZONTAL);
|
||||||
|
|
||||||
|
if (bitmap.getHeight() != view.getHeight()) {
|
||||||
|
ColorDrawable colorDrawable = new ColorDrawable(mColor);
|
||||||
|
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{ colorDrawable, drawable });
|
||||||
|
return layerDrawable;
|
||||||
|
} else {
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.LinearGradient;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.PorterDuff.Mode;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
|
||||||
|
public class LightweightThemeDrawable extends BitmapDrawable
|
||||||
|
implements CanvasDelegate.DrawManager {
|
||||||
|
private static final String LOGTAG = "GeckoLightweightThemeDrawable";
|
||||||
|
private Path mPath;
|
||||||
|
private CanvasDelegate mCanvasDelegate;
|
||||||
|
private Bitmap mBitmap;
|
||||||
|
private int mStartColor;
|
||||||
|
private int mEndColor;
|
||||||
|
|
||||||
|
public LightweightThemeDrawable(Resources resources, Bitmap bitmap) {
|
||||||
|
super(resources, bitmap);
|
||||||
|
mBitmap = bitmap;
|
||||||
|
|
||||||
|
mPath = new Path();
|
||||||
|
mCanvasDelegate = new CanvasDelegate(this, Mode.DST_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlpha(int startAlpha, int endAlpha) {
|
||||||
|
mStartColor = startAlpha << 24;
|
||||||
|
mEndColor = endAlpha << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBoundsChange(Rect bounds) {
|
||||||
|
mCanvasDelegate.setShader(new LinearGradient(0, 0,
|
||||||
|
0, mBitmap.getHeight(),
|
||||||
|
mStartColor, mEndColor,
|
||||||
|
Shader.TileMode.CLAMP));
|
||||||
|
|
||||||
|
mPath.addRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), Path.Direction.CW);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
mCanvasDelegate.draw(canvas, mPath, canvas.getWidth(), canvas.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void defaultDraw(Canvas canvas) {
|
||||||
|
super.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,6 +92,8 @@ FENNEC_JAVA_FILES = \
|
||||||
GeckoViewsFactory.java \
|
GeckoViewsFactory.java \
|
||||||
HeightChangeAnimation.java \
|
HeightChangeAnimation.java \
|
||||||
InputMethods.java \
|
InputMethods.java \
|
||||||
|
LightweightTheme.java \
|
||||||
|
LightweightThemeDrawable.java \
|
||||||
LinkPreference.java \
|
LinkPreference.java \
|
||||||
LinkTextView.java \
|
LinkTextView.java \
|
||||||
MemoryMonitor.java \
|
MemoryMonitor.java \
|
||||||
|
|
|
@ -10,6 +10,10 @@ import android.graphics.Color;
|
||||||
|
|
||||||
public final class BitmapUtils {
|
public final class BitmapUtils {
|
||||||
public static int getDominantColor(Bitmap source) {
|
public static int getDominantColor(Bitmap source) {
|
||||||
|
return getDominantColor(source, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDominantColor(Bitmap source, boolean applyThreshold) {
|
||||||
int[] colors = new int[37];
|
int[] colors = new int[37];
|
||||||
int[] sat = new int[11];
|
int[] sat = new int[11];
|
||||||
int[] val = new int[11];
|
int[] val = new int[11];
|
||||||
|
@ -29,15 +33,18 @@ public final class BitmapUtils {
|
||||||
Color.colorToHSV(c, hsv);
|
Color.colorToHSV(c, hsv);
|
||||||
|
|
||||||
// arbitrarily chosen values for "white" and "black"
|
// arbitrarily chosen values for "white" and "black"
|
||||||
if (hsv[1] > 0.35f && hsv[2] > 0.35f) {
|
if (applyThreshold && hsv[1] <= 0.35f && hsv[2] <= 0.35f)
|
||||||
|
continue;
|
||||||
|
|
||||||
int h = Math.round(hsv[0] / 10.0f);
|
int h = Math.round(hsv[0] / 10.0f);
|
||||||
int s = Math.round(hsv[1] * 10.0f);
|
int s = Math.round(hsv[1] * 10.0f);
|
||||||
int v = Math.round(hsv[2] * 10.0f);
|
int v = Math.round(hsv[2] * 10.0f);
|
||||||
colors[h]++;
|
colors[h]++;
|
||||||
sat[s]++;
|
sat[s]++;
|
||||||
val[v]++;
|
val[v]++;
|
||||||
// we only care about the most unique non white or black hue, but also
|
|
||||||
// store its saturation and value params to match the color better
|
// we only care about the most unique non white or black hue - if threshold is applied
|
||||||
|
// we also store its saturation and value params to match the color better
|
||||||
if (colors[h] > colors[maxH]) {
|
if (colors[h] > colors[maxH]) {
|
||||||
maxH = h;
|
maxH = h;
|
||||||
maxS = s;
|
maxS = s;
|
||||||
|
@ -45,7 +52,6 @@ public final class BitmapUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
float[] hsv = new float[3];
|
float[] hsv = new float[3];
|
||||||
hsv[0] = maxH*10.0f;
|
hsv[0] = maxH*10.0f;
|
||||||
hsv[1] = (float)maxS/10.0f;
|
hsv[1] = (float)maxS/10.0f;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче