Minor changes everywhere, incremental progress on WindingNumber

This commit is contained in:
Sridhar Ramesh
2018-02-06 12:44:38 -08:00
parent 5ea41c00ee
commit 6158ba6dcc
6 changed files with 133 additions and 58 deletions

View File

@ -67,10 +67,14 @@ class EquationSolver1d(GraphScene, ZoomedScene):
self.add(target_line_label) self.add(target_line_label)
def solveEquation(self): def solveEquation(self):
leftBrace, rightBrace = xBraces = TexMobject("||") leftBrace = TexMobject("[")
rightBrace = TexMobject("]")
xBraces = Group(leftBrace, rightBrace)
xBraces.stretch(2, 0) xBraces.stretch(2, 0)
downBrace, upBrace = yBraces = TexMobject("||") downBrace = TexMobject("[")
upBrace = TexMobject("]")
yBraces = Group(downBrace, upBrace)
yBraces.stretch(2, 0) yBraces.stretch(2, 0)
yBraces.rotate(TAU/4) yBraces.rotate(TAU/4)
@ -79,7 +83,7 @@ class EquationSolver1d(GraphScene, ZoomedScene):
upperX = self.initial_upper_x upperX = self.initial_upper_x
upperY = self.func(upperX) upperY = self.func(upperX)
leftBrace.move_to(self.coords_to_point(lowerX, 0)) leftBrace.move_to(self.coords_to_point(lowerX, 0), aligned_edge = LEFT)
leftBraceLabel = DecimalNumber(lowerX) leftBraceLabel = DecimalNumber(lowerX)
leftBraceLabel.next_to(leftBrace, DOWN + LEFT, buff = SMALL_BUFF) leftBraceLabel.next_to(leftBrace, DOWN + LEFT, buff = SMALL_BUFF)
leftBraceLabelAnimation = ContinualChangingDecimal(leftBraceLabel, leftBraceLabelAnimation = ContinualChangingDecimal(leftBraceLabel,
@ -87,7 +91,7 @@ class EquationSolver1d(GraphScene, ZoomedScene):
tracked_mobject = leftBrace) tracked_mobject = leftBrace)
self.add(leftBraceLabelAnimation) self.add(leftBraceLabelAnimation)
rightBrace.move_to(self.coords_to_point(upperX, 0)) rightBrace.move_to(self.coords_to_point(upperX, 0), aligned_edge = RIGHT)
rightBraceLabel = DecimalNumber(upperX) rightBraceLabel = DecimalNumber(upperX)
rightBraceLabel.next_to(rightBrace, DOWN + RIGHT, buff = SMALL_BUFF) rightBraceLabel.next_to(rightBrace, DOWN + RIGHT, buff = SMALL_BUFF)
rightBraceLabelAnimation = ContinualChangingDecimal(rightBraceLabel, rightBraceLabelAnimation = ContinualChangingDecimal(rightBraceLabel,
@ -95,7 +99,7 @@ class EquationSolver1d(GraphScene, ZoomedScene):
tracked_mobject = rightBrace) tracked_mobject = rightBrace)
self.add(rightBraceLabelAnimation) self.add(rightBraceLabelAnimation)
downBrace.move_to(self.coords_to_point(0, lowerY)) downBrace.move_to(self.coords_to_point(0, lowerY), aligned_edge = DOWN)
downBraceLabel = DecimalNumber(lowerY) downBraceLabel = DecimalNumber(lowerY)
downBraceLabel.next_to(downBrace, LEFT + DOWN, buff = SMALL_BUFF) downBraceLabel.next_to(downBrace, LEFT + DOWN, buff = SMALL_BUFF)
downBraceLabelAnimation = ContinualChangingDecimal(downBraceLabel, downBraceLabelAnimation = ContinualChangingDecimal(downBraceLabel,
@ -103,7 +107,7 @@ class EquationSolver1d(GraphScene, ZoomedScene):
tracked_mobject = downBrace) tracked_mobject = downBrace)
self.add(downBraceLabelAnimation) self.add(downBraceLabelAnimation)
upBrace.move_to(self.coords_to_point(0, upperY)) upBrace.move_to(self.coords_to_point(0, upperY), aligned_edge = UP)
upBraceLabel = DecimalNumber(upperY) upBraceLabel = DecimalNumber(upperY)
upBraceLabel.next_to(upBrace, LEFT + UP, buff = SMALL_BUFF) upBraceLabel.next_to(upBrace, LEFT + UP, buff = SMALL_BUFF)
upBraceLabelAnimation = ContinualChangingDecimal(upBraceLabel, upBraceLabelAnimation = ContinualChangingDecimal(upBraceLabel,
@ -200,7 +204,7 @@ class EquationSolver1d(GraphScene, ZoomedScene):
self.drawGraph() self.drawGraph()
self.solveEquation() self.solveEquation()
colorslist = map(color_to_rgba, ["#FF0000", ORANGE, YELLOW, "#00FF00", "#0000FF", "#FF00FF"]) colorslist = map(color_to_rgba, ["#FF0000", "#FFFF00", "#00FF00", "#0000FF"])
def rev_to_rgba(alpha): def rev_to_rgba(alpha):
alpha = alpha % 1 alpha = alpha % 1
@ -330,9 +334,7 @@ def point_func_from_complex_func(f):
test_map_func = point_func_from_complex_func(lambda c: c**2) test_map_func = point_func_from_complex_func(lambda c: c**2)
empty_animation = Animation(Mobject(), run_time = 0) empty_animation = EmptyAnimation()
def EmptyAnimation():
return empty_animation
class WalkerAnimation(Animation): class WalkerAnimation(Animation):
CONFIG = { CONFIG = {
@ -347,6 +349,8 @@ class WalkerAnimation(Animation):
self.rev_func = rev_func self.rev_func = rev_func
self.coords_to_point = coords_to_point self.coords_to_point = coords_to_point
self.compound_walker = VGroup() self.compound_walker = VGroup()
self.show_arrows = show_arrows
base_walker = Dot().scale(5) # PiCreature().scale(0.8) # base_walker = Dot().scale(5) # PiCreature().scale(0.8) #
self.compound_walker.walker = base_walker.scale(0.35).set_stroke(BLACK, 1.5) #PiCreature() self.compound_walker.walker = base_walker.scale(0.35).set_stroke(BLACK, 1.5) #PiCreature()
if show_arrows: if show_arrows:
@ -365,7 +369,7 @@ class WalkerAnimation(Animation):
self.mobject.shift(cur_point - self.mobject.walker.get_center()) self.mobject.shift(cur_point - self.mobject.walker.get_center())
rev = self.rev_func(cur_coords) rev = self.rev_func(cur_coords)
self.mobject.walker.set_fill(rev_to_color(rev)) self.mobject.walker.set_fill(rev_to_color(rev))
if show_arrows: if self.show_arrows:
self.mobject.arrow.set_fill(rev_to_color(rev)) self.mobject.arrow.set_fill(rev_to_color(rev))
self.mobject.arrow.rotate( self.mobject.arrow.rotate(
rev * TAU, rev * TAU,
@ -430,14 +434,23 @@ class ColorMappedByFuncScene(Scene):
CONFIG = { CONFIG = {
"func" : lambda p : p, "func" : lambda p : p,
"num_plane" : NumberPlane(), "num_plane" : NumberPlane(),
"display_output_color_map" : False "show_num_plane" : True,
"show_output" : False, # Not currently implemented; TODO
} }
def construct(self): def setup(self):
display_func = self.func if not self.display_output_color_map else lambda p : p if self.show_output:
self.pos_func = self.func
else:
self.pos_func = lambda p : p
self.num_plane.fade() def construct(self):
self.add(self.num_plane) display_func = self.func if not self.show_output else lambda p : p
if self.show_num_plane:
self.num_plane.fade()
self.add(self.num_plane)
self.camera.set_background_from_func( self.camera.set_background_from_func(
lambda (x, y): point_to_rgba( lambda (x, y): point_to_rgba(
display_func( display_func(
@ -449,11 +462,15 @@ class ColorMappedByFuncScene(Scene):
) )
) )
class ColorMappedByFuncStill(ColorMappedByFuncScene):
def construct(self):
ColorMappedByFuncScene.construct(self)
self.wait()
class PiWalker(ColorMappedByFuncScene): class PiWalker(ColorMappedByFuncScene):
CONFIG = { CONFIG = {
"walk_coords" : [], "walk_coords" : [],
"step_run_time" : 1, "step_run_time" : 1,
"show_arrows" : True
} }
def construct(self): def construct(self):
@ -476,7 +493,7 @@ class PiWalker(ColorMappedByFuncScene):
coords_to_point = num_plane.coords_to_point, coords_to_point = num_plane.coords_to_point,
rev_func = rev_func, rev_func = rev_func,
remover = (i < len(walk_coords) - 1), remover = (i < len(walk_coords) - 1),
show_arrows = self.show_arrows show_arrows = not self.show_output
), ),
run_time = self.step_run_time) run_time = self.step_run_time)
@ -515,9 +532,7 @@ class PiWalkerCircle(PiWalker):
self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)] self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)]
PiWalker.setup(self) PiWalker.setup(self)
# TODO: Perhaps restructure this to avoid using AnimationGroup, and instead # TODO: Give drawn lines a bit of buffer, so that the rectangle's corners are filled in
# use lists of animations or lists or other such data, to be merged and processed into parallel
# animations later
class EquationSolver2d(ColorMappedByFuncScene): class EquationSolver2d(ColorMappedByFuncScene):
CONFIG = { CONFIG = {
"initial_lower_x" : -5.1, "initial_lower_x" : -5.1,
@ -526,7 +541,8 @@ class EquationSolver2d(ColorMappedByFuncScene):
"initial_upper_y" : 3.1, "initial_upper_y" : 3.1,
"num_iterations" : 5, "num_iterations" : 5,
"num_checkpoints" : 10, "num_checkpoints" : 10,
"display_in_parallel" : True "display_in_parallel" : True,
"use_fancy_lines" : True,
# TODO: Consider adding a "find_all_roots" flag, which could be turned off # TODO: Consider adding a "find_all_roots" flag, which could be turned off
# to only explore one of the two candidate subrectangles when both are viable # to only explore one of the two candidate subrectangles when both are viable
} }
@ -534,23 +550,32 @@ class EquationSolver2d(ColorMappedByFuncScene):
def construct(self): def construct(self):
ColorMappedByFuncScene.construct(self) ColorMappedByFuncScene.construct(self)
num_plane = self.num_plane num_plane = self.num_plane
self.remove(num_plane)
background = self.camera.background
self.camera.init_background() # Clearing background
rev_func = lambda p : point_to_rev(self.func(p)) rev_func = lambda p : point_to_rev(self.func(p))
clockwise_rev_func = lambda p : -rev_func(p) clockwise_rev_func = lambda p : -rev_func(p)
def Animate2dSolver(cur_depth, rect, dim_to_split): def Animate2dSolver(cur_depth, rect, dim_to_split, sides_to_draw = [0, 1, 2, 3]):
print "Solver at depth: " + str(cur_depth) print "Solver at depth: " + str(cur_depth)
if cur_depth >= self.num_iterations: if cur_depth >= self.num_iterations:
return EmptyAnimation() return empty_animation
def draw_line_return_wind(start, end, start_wind, should_linger = False): def draw_line_return_wind(start, end, start_wind, should_linger = False, draw_line = True):
alpha_winder = make_alpha_winder(clockwise_rev_func, start, end, self.num_checkpoints) alpha_winder = make_alpha_winder(clockwise_rev_func, start, end, self.num_checkpoints)
a0 = alpha_winder(0) a0 = alpha_winder(0)
rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind
thin_line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end), thick_line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end),
stroke_width = 2, stroke_width = 10,
color = RED) color = RED)
if self.use_fancy_lines:
colored_line = BackgroundColoredVMobject(thick_line, background_image_file = None)
colored_line.set_background_array(background)
else:
colored_line = thick_line.set_stroke_with(4)
walker_anim = LinearWalker( walker_anim = LinearWalker(
start_coords = start, start_coords = start,
@ -566,14 +591,16 @@ class EquationSolver2d(ColorMappedByFuncScene):
else: else:
rate_func = None rate_func = None
opt_line_anim = ShowCreation(colored_line) if draw_line else empty_animation
line_draw_anim = AnimationGroup( line_draw_anim = AnimationGroup(
ShowCreation(thin_line), opt_line_anim,
walker_anim, walker_anim,
rate_func = rate_func) rate_func = rate_func)
return (line_draw_anim, rebased_winder(1)) return (line_draw_anim, rebased_winder(1))
wind_so_far = 0 wind_so_far = 0
anim = EmptyAnimation() anim = empty_animation
sides = [ sides = [
rect.get_top(), rect.get_top(),
rect.get_right(), rect.get_right(),
@ -582,7 +609,8 @@ class EquationSolver2d(ColorMappedByFuncScene):
] ]
for (i, (start, end)) in enumerate(sides): for (i, (start, end)) in enumerate(sides):
(next_anim, wind_so_far) = draw_line_return_wind(start, end, wind_so_far, (next_anim, wind_so_far) = draw_line_return_wind(start, end, wind_so_far,
should_linger = i == len(sides) - 1) should_linger = i == len(sides) - 1,
draw_line = i in sides_to_draw)
anim = Succession(anim, next_anim) anim = Succession(anim, next_anim)
total_wind = round(wind_so_far) total_wind = round(wind_so_far)
@ -594,32 +622,36 @@ class EquationSolver2d(ColorMappedByFuncScene):
rect.get_bottom_right(), rect.get_bottom_right(),
rect.get_bottom_left() rect.get_bottom_left()
] ]
points = [num_plane.coords_to_point(x, y) for (x, y) in coords] points = np.array([num_plane.coords_to_point(x, y) for (x, y) in coords]) + IN
# TODO: Maybe use diagonal lines or something to fill in rectangles indicating # TODO: Maybe use diagonal lines or something to fill in rectangles indicating
# their "Nothing here" status? # their "Nothing here" status?
fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = RED) # Or draw a large X or something
fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = GREY)
return Succession(anim, FadeIn(fill_rect)) return Succession(anim, FadeIn(fill_rect))
else: else:
(sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split) (sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split)
sub_rects = [sub_rect1, sub_rect2] if dim_to_split == 0:
sub_rect_and_sides = [(sub_rect1, 1), (sub_rect2, 3)]
else:
sub_rect_and_sides = [(sub_rect1, 2), (sub_rect2, 0)]
sub_anims = [ sub_anims = [
Animate2dSolver( Animate2dSolver(
cur_depth = cur_depth + 1, cur_depth = cur_depth + 1,
rect = sub_rect, rect = sub_rect,
dim_to_split = 1 - dim_to_split dim_to_split = 1 - dim_to_split,
sides_to_draw = [side_to_draw]
) )
for sub_rect in sub_rects for (sub_rect, side_to_draw) in sub_rect_and_sides
] ]
mid_line_coords = rect.split_line_on_dim(dim_to_split) mid_line_coords = rect.split_line_on_dim(dim_to_split)
mid_line_points = [num_plane.coords_to_point(x, y) for (x, y) in mid_line_coords] mid_line_points = [num_plane.coords_to_point(x, y) for (x, y) in mid_line_coords]
mid_line = DashedLine(*mid_line_points) # TODO: Have this match rectangle line style, apart from dashes and thin-ness? # TODO: Have this match rectangle line style, apart from dashes and thin-ness?
if len(sub_anims) > 0: # Though there is also informational value in seeing the dashed line separately from rectangle lines
if self.display_in_parallel: mid_line = DashedLine(*mid_line_points)
recursive_anim = AnimationGroup(*sub_anims) if self.display_in_parallel:
else: recursive_anim = AnimationGroup(*sub_anims)
recursive_anim = Succession(*sub_anims)
else: else:
recursive_anim = empty_animation # Have to do this because Succession doesn't currently handle empty animations recursive_anim = Succession(*sub_anims)
return Succession(anim, return Succession(anim,
ShowCreation(mid_line), ShowCreation(mid_line),
recursive_anim recursive_anim
@ -780,7 +812,7 @@ class FirstSqrtScene(EquationSolver1d):
"targetY" : 2, "targetY" : 2,
"initial_lower_x" : 1, "initial_lower_x" : 1,
"initial_upper_x" : 2, "initial_upper_x" : 2,
"num_iterations" : 10, "num_iterations" : 3,
"iteration_at_which_to_start_zoom" : 3, "iteration_at_which_to_start_zoom" : 3,
"graph_label" : "y = x^2", "graph_label" : "y = x^2",
"show_target_line" : True, "show_target_line" : True,
@ -1096,8 +1128,8 @@ class LoopSplitSceneMapped(LoopSplitScene):
class FundThmAlg(EquationSolver2d): class FundThmAlg(EquationSolver2d):
CONFIG = { CONFIG = {
"func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)), "func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)),
"num_iterations" : 4, "num_iterations" : 5,
"display_in_parallel" : False "display_in_parallel" : True
} }
# TODO: Borsuk-Ulam visuals # TODO: Borsuk-Ulam visuals
@ -1159,11 +1191,23 @@ class DiffOdometer(OdometerScene):
#################### ####################
# Random test scenes and test functions go here:
def rect_to_circle((x, y, z)):
size = np.sqrt(x**2 + y**2)
max_abs_size = max(abs(x), abs(y))
return fdiv(np.array((x, y, z)) * max_abs_size, size)
class MapPiWalkerRect(PiWalkerRect): class MapPiWalkerRect(PiWalkerRect):
CONFIG = { CONFIG = {
"camera_class" : MappingCamera, "camera_class" : MappingCamera,
"camera_config" : {"mapping_func" : test_map_func}, "camera_config" : {"mapping_func" : rect_to_circle},
"display_output_color_map" : True "display_output_color_map" : True
} }
class ShowBack(PiWalkerRect):
CONFIG = {
"func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5))
}
# FIN # FIN

View File

@ -22,6 +22,9 @@ class Animation(object):
#one_at_a_time, all_at_once #one_at_a_time, all_at_once
"submobject_mode" : "all_at_once", "submobject_mode" : "all_at_once",
"lag_factor" : 2, "lag_factor" : 2,
# Used by EmptyAnimation to announce itself ignorable
# in Successions and AnimationGroups
"empty" : False
} }
def __init__(self, mobject, **kwargs): def __init__(self, mobject, **kwargs):
mobject = instantiate(mobject) mobject = instantiate(mobject)

View File

@ -403,7 +403,7 @@ class Succession(Animation):
for anim in animations: for anim in animations:
anim.update(0) anim.update(0)
animations = filter (lambda x : x.run_time != 0, animations) animations = filter (lambda x : not(x.empty), animations)
self.run_times = [anim.run_time for anim in animations] self.run_times = [anim.run_time for anim in animations]
if "run_time" in kwargs: if "run_time" in kwargs:
@ -411,6 +411,8 @@ class Succession(Animation):
else: else:
run_time = sum(self.run_times) run_time = sum(self.run_times)
self.num_anims = len(animations) self.num_anims = len(animations)
if self.num_anims == 0:
self.empty = True
self.animations = animations self.animations = animations
#Have to keep track of this run_time, because Scene.play #Have to keep track of this run_time, because Scene.play
#might very well mess with it. #might very well mess with it.
@ -485,8 +487,14 @@ class AnimationGroup(Animation):
} }
def __init__(self, *sub_anims, **kwargs): def __init__(self, *sub_anims, **kwargs):
digest_config(self, kwargs, locals()) digest_config(self, kwargs, locals())
sync_animation_run_times_and_rate_funcs(*sub_anims, **kwargs) sub_anims = filter (lambda x : not(x.empty), sub_anims)
self.run_time = max([a.run_time for a in sub_anims]) if len(sub_anims) == 0:
self.empty = True
self.run_time = 0
else:
# Should really make copies of animations, instead of messing with originals...
sync_animation_run_times_and_rate_funcs(*sub_anims, **kwargs)
self.run_time = max([a.run_time for a in sub_anims])
everything = Mobject(*[a.mobject for a in sub_anims]) everything = Mobject(*[a.mobject for a in sub_anims])
Animation.__init__(self, everything, **kwargs) Animation.__init__(self, everything, **kwargs)
@ -497,3 +505,12 @@ class AnimationGroup(Animation):
def clean_up(self, *args, **kwargs): def clean_up(self, *args, **kwargs):
for anim in self.sub_anims: for anim in self.sub_anims:
anim.clean_up(*args, **kwargs) anim.clean_up(*args, **kwargs)
class EmptyAnimation(Animation):
CONFIG = {
"run_time" : 0,
"empty" : True
}
def __init__(self, *args, **kwargs):
return Animation.__init__(self, Group(), *args, **kwargs)

View File

@ -99,7 +99,7 @@ class Camera(object):
def set_background(self, pixel_array, convert_from_floats = False): def set_background(self, pixel_array, convert_from_floats = False):
self.background = self.convert_pixel_array(pixel_array, convert_from_floats) self.background = self.convert_pixel_array(pixel_array, convert_from_floats)
def set_background_from_func(self, coords_to_colors_func): def make_background_from_func(self, coords_to_colors_func):
""" """
Sets background by using coords_to_colors_func to determine each pixel's color. Each input Sets background by using coords_to_colors_func to determine each pixel's color. Each input
to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not
@ -114,9 +114,10 @@ class Camera(object):
2, 2,
coords coords
) )
self.set_background(new_background, convert_from_floats = True) return self.convert_pixel_array(new_background, convert_from_floats = True)
print "Ending set_background_from_func" def set_background_from_func(self, coords_to_colors_func):
self.set_background(self.make_background_from_func(coords_to_colors_func))
def reset(self): def reset(self):
self.set_pixel_array(self.background) self.set_pixel_array(self.background)
@ -500,7 +501,8 @@ class MovingCamera(Camera):
0 if self.aligned_dimension == "height" else 1 0 if self.aligned_dimension == "height" else 1
) )
# TODO: Add an attribute to mobjects under which they can specify that they should just
# map their centers but remain otherwise undistorted (useful for labels, etc.)
class MappingCamera(Camera): class MappingCamera(Camera):
CONFIG = { CONFIG = {
"mapping_func" : lambda p : p, "mapping_func" : lambda p : p,

View File

@ -544,12 +544,16 @@ def wiggle(t, wiggles = 2):
def squish_rate_func(func, a = 0.4, b = 0.6): def squish_rate_func(func, a = 0.4, b = 0.6):
def result(t): def result(t):
if a == b:
return a
if t < a: if t < a:
return func(0) return func(0)
elif t > b: elif t > b:
return func(1) return func(1)
else: else:
return func((t-a)/(b-a)) return func((t-a)/(b-a))
return result return result
# Stylistically, should this take parameters (with default values)? # Stylistically, should this take parameters (with default values)?

View File

@ -459,7 +459,8 @@ class VectorizedPoint(VMobject):
class BackgroundColoredVMobject(VMobject): class BackgroundColoredVMobject(VMobject):
CONFIG = { CONFIG = {
"background_image" : "color_background", # Can be set to None, using set_background_array to initialize instead
"background_image_file" : "color_background",
"stroke_color" : WHITE, "stroke_color" : WHITE,
"fill_color" : WHITE, "fill_color" : WHITE,
} }
@ -475,10 +476,14 @@ class BackgroundColoredVMobject(VMobject):
for submob in vmobject.submobjects: for submob in vmobject.submobjects:
self.add(BackgroundColoredVMobject(submob, **kwargs)) self.add(BackgroundColoredVMobject(submob, **kwargs))
#Initialize background array if self.background_image_file != None:
path = get_full_raster_image_path(self.background_image) #Initialize background array
image = Image.open(path) path = get_full_raster_image_path(self.background_image_file)
self.background_array = np.array(image) image = Image.open(path)
self.set_background_array(np.array(image))
def set_background_array(self, background_array):
self.background_array = background_array
def resize_background_array(self, new_width, new_height, mode = "RGBA"): def resize_background_array(self, new_width, new_height, mode = "RGBA"):
image = Image.fromarray(self.background_array, mode = mode) image = Image.fromarray(self.background_array, mode = mode)